2023-05-10 07:55:36 +00:00
|
|
|
|
using DistTestCore.Codex;
|
|
|
|
|
using DistTestCore;
|
|
|
|
|
using NUnit.Framework;
|
|
|
|
|
using Logging;
|
2023-05-12 08:48:12 +00:00
|
|
|
|
using Utils;
|
2023-05-10 07:55:36 +00:00
|
|
|
|
|
|
|
|
|
namespace Tests.PeerDiscoveryTests
|
|
|
|
|
{
|
|
|
|
|
public static class PeerTestHelpers
|
|
|
|
|
{
|
2023-05-18 08:42:04 +00:00
|
|
|
|
private static readonly Random random = new Random();
|
|
|
|
|
|
2023-05-10 07:55:36 +00:00
|
|
|
|
public static void AssertFullyConnected(IEnumerable<IOnlineCodexNode> nodes, BaseLog? log = null)
|
|
|
|
|
{
|
|
|
|
|
AssertFullyConnected(log, nodes.ToArray());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void AssertFullyConnected(BaseLog? log = null, params IOnlineCodexNode[] nodes)
|
|
|
|
|
{
|
2023-05-12 08:48:12 +00:00
|
|
|
|
var entries = CreateEntries(nodes);
|
|
|
|
|
var pairs = CreatePairs(entries);
|
2023-05-10 08:47:10 +00:00
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
RetryWhilePairs(pairs, () =>
|
2023-05-11 11:59:53 +00:00
|
|
|
|
{
|
2023-05-12 08:48:12 +00:00
|
|
|
|
CheckAndRemoveSuccessful(pairs, log);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (pairs.Any())
|
|
|
|
|
{
|
|
|
|
|
Assert.Fail(string.Join(Environment.NewLine, pairs.Select(p => p.GetMessage())));
|
2023-05-11 11:59:53 +00:00
|
|
|
|
}
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
private static void RetryWhilePairs(List<Pair> pairs, Action action)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2023-05-18 08:42:04 +00:00
|
|
|
|
var timeout = DateTime.UtcNow + TimeSpan.FromMinutes(5);
|
2023-05-12 08:48:12 +00:00
|
|
|
|
while (pairs.Any() && (timeout > DateTime.UtcNow))
|
|
|
|
|
{
|
|
|
|
|
action();
|
2023-05-12 07:11:05 +00:00
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
if (pairs.Any()) Time.Sleep(TimeSpan.FromSeconds(5));
|
|
|
|
|
}
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
private static void CheckAndRemoveSuccessful(List<Pair> pairs, BaseLog? log)
|
2023-05-12 07:11:05 +00:00
|
|
|
|
{
|
2023-05-12 08:48:12 +00:00
|
|
|
|
var checkTasks = pairs.Select(p => Task.Run(p.Check)).ToArray();
|
|
|
|
|
Task.WaitAll(checkTasks);
|
2023-05-12 07:11:05 +00:00
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
foreach (var pair in pairs.ToArray())
|
2023-05-12 07:11:05 +00:00
|
|
|
|
{
|
2023-05-12 08:48:12 +00:00
|
|
|
|
if (pair.Success)
|
|
|
|
|
{
|
|
|
|
|
pairs.Remove(pair);
|
|
|
|
|
if (log != null) log.Log(pair.GetMessage());
|
|
|
|
|
}
|
2023-05-18 08:42:04 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pair.IncreaseTimeout();
|
|
|
|
|
}
|
2023-05-12 07:11:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
private static Entry[] CreateEntries(IOnlineCodexNode[] nodes)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2023-05-12 08:48:12 +00:00
|
|
|
|
return nodes.Select(n => new Entry(n)).ToArray();
|
|
|
|
|
}
|
2023-05-10 08:47:10 +00:00
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
private static List<Pair> CreatePairs(Entry[] entries)
|
|
|
|
|
{
|
|
|
|
|
return CreatePairsIterator(entries).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IEnumerable<Pair> CreatePairsIterator(Entry[] entries)
|
|
|
|
|
{
|
|
|
|
|
for (var x = 0; x < entries.Length; x++)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2023-05-12 08:48:12 +00:00
|
|
|
|
for (var y = x + 1; y < entries.Length; y++)
|
2023-05-11 10:44:53 +00:00
|
|
|
|
{
|
2023-05-12 08:48:12 +00:00
|
|
|
|
yield return new Pair(entries[x], entries[y]);
|
2023-05-11 10:44:53 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Entry
|
|
|
|
|
{
|
|
|
|
|
public Entry(IOnlineCodexNode node)
|
2023-05-10 08:47:10 +00:00
|
|
|
|
{
|
2023-05-11 10:44:53 +00:00
|
|
|
|
Node = node;
|
|
|
|
|
Response = node.GetDebugInfo();
|
2023-05-10 08:47:10 +00:00
|
|
|
|
}
|
2023-05-11 10:44:53 +00:00
|
|
|
|
|
|
|
|
|
public IOnlineCodexNode Node { get ; }
|
|
|
|
|
public CodexDebugResponse Response { get; }
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
2023-05-12 08:48:12 +00:00
|
|
|
|
|
|
|
|
|
public class Pair
|
|
|
|
|
{
|
2023-05-18 08:42:04 +00:00
|
|
|
|
private TimeSpan timeout = TimeSpan.FromSeconds(60);
|
|
|
|
|
private TimeSpan aToBTime = TimeSpan.FromSeconds(0);
|
|
|
|
|
private TimeSpan bToATime = TimeSpan.FromSeconds(0);
|
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
public Pair(Entry a, Entry b)
|
|
|
|
|
{
|
|
|
|
|
A = a;
|
|
|
|
|
B = b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Entry A { get; }
|
|
|
|
|
public Entry B { get; }
|
|
|
|
|
public bool AKnowsB { get; private set; }
|
|
|
|
|
public bool BKnowsA { get; private set; }
|
|
|
|
|
public bool Success { get { return AKnowsB && BKnowsA; } }
|
|
|
|
|
|
|
|
|
|
public void Check()
|
|
|
|
|
{
|
2023-05-18 08:42:04 +00:00
|
|
|
|
ApplyRandomDelay();
|
|
|
|
|
aToBTime = Measure(() => AKnowsB = Knows(A, B));
|
|
|
|
|
bToATime = Measure(() => BKnowsA = Knows(B, A));
|
2023-05-12 08:48:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetMessage()
|
2023-05-18 08:42:04 +00:00
|
|
|
|
{
|
|
|
|
|
return GetResultMessage() + GetTimePostfix();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void IncreaseTimeout()
|
|
|
|
|
{
|
|
|
|
|
//timeout *= 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string GetResultMessage()
|
2023-05-12 08:48:12 +00:00
|
|
|
|
{
|
|
|
|
|
var aName = A.Response.id;
|
|
|
|
|
var bName = B.Response.id;
|
|
|
|
|
|
|
|
|
|
if (AKnowsB && BKnowsA)
|
|
|
|
|
{
|
|
|
|
|
return $"{aName} and {bName} know each other.";
|
|
|
|
|
}
|
|
|
|
|
if (AKnowsB)
|
|
|
|
|
{
|
|
|
|
|
return $"{aName} knows {bName}, but {bName} does not know {aName}";
|
|
|
|
|
}
|
|
|
|
|
if (BKnowsA)
|
|
|
|
|
{
|
|
|
|
|
return $"{bName} knows {aName}, but {aName} does not know {bName}";
|
|
|
|
|
}
|
|
|
|
|
return $"{aName} and {bName} don't know each other.";
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 08:42:04 +00:00
|
|
|
|
private string GetTimePostfix()
|
2023-05-12 08:48:12 +00:00
|
|
|
|
{
|
2023-05-18 08:42:04 +00:00
|
|
|
|
var aName = A.Response.id;
|
|
|
|
|
var bName = B.Response.id;
|
2023-05-12 08:48:12 +00:00
|
|
|
|
|
2023-05-18 08:42:04 +00:00
|
|
|
|
return $" ({aName}->{bName}: {aToBTime.TotalMinutes} seconds, {bName}->{aName}: {bToATime.TotalSeconds} seconds)";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ApplyRandomDelay()
|
|
|
|
|
{
|
|
|
|
|
// Calling all the nodes all at the same time is not exactly nice.
|
|
|
|
|
Time.Sleep(TimeSpan.FromMicroseconds(random.Next(10, 100)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TimeSpan Measure(Action action)
|
|
|
|
|
{
|
|
|
|
|
var start = DateTime.UtcNow;
|
|
|
|
|
action();
|
|
|
|
|
return DateTime.UtcNow - start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool Knows(Entry a, Entry b)
|
|
|
|
|
{
|
|
|
|
|
lock (a)
|
2023-05-12 08:48:12 +00:00
|
|
|
|
{
|
2023-05-18 08:42:04 +00:00
|
|
|
|
var peerId = b.Response.id;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var response = a.Node.GetDebugPeer(peerId, timeout);
|
|
|
|
|
if (!string.IsNullOrEmpty(response.peerId) && response.addresses.Any())
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
2023-05-12 08:48:12 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-18 08:42:04 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2023-05-12 08:48:12 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|