cs-codex-dist-tests/Tests/PeerDiscoveryTests/PeerTestHelpers.cs

219 lines
7.1 KiB
C#
Raw Normal View History

using DistTestCore.Codex;
using DistTestCore;
using NUnit.Framework;
using Logging;
2023-05-12 08:48:12 +00:00
using Utils;
namespace Tests.PeerDiscoveryTests
{
public static class PeerTestHelpers
{
private static readonly Random random = new Random();
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-12 08:48:12 +00:00
private static void RetryWhilePairs(List<Pair> pairs, Action action)
{
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 08:48:12 +00:00
if (pairs.Any()) Time.Sleep(TimeSpan.FromSeconds(5));
}
}
2023-05-12 08:48:12 +00:00
private static void CheckAndRemoveSuccessful(List<Pair> pairs, BaseLog? log)
{
2023-05-12 08:48:12 +00:00
var checkTasks = pairs.Select(p => Task.Run(p.Check)).ToArray();
Task.WaitAll(checkTasks);
2023-05-12 08:48:12 +00:00
foreach (var pair in pairs.ToArray())
{
2023-05-12 08:48:12 +00:00
if (pair.Success)
{
pairs.Remove(pair);
}
}
}
2023-05-12 08:48:12 +00:00
private static Entry[] CreateEntries(IOnlineCodexNode[] nodes)
{
var entries = nodes.Select(n => new Entry(n)).ToArray();
var incorrectDiscoveryEndpoints = entries.SelectMany(e => e.GetInCorrectDiscoveryEndpoints(entries)).ToArray();
if (incorrectDiscoveryEndpoints.Any())
{
Assert.Fail("Some nodes contain peer records with incorrect discovery ip/port information: " +
string.Join(Environment.NewLine, incorrectDiscoveryEndpoints));
}
return entries;
2023-05-12 08:48:12 +00:00
}
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-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; }
public IEnumerable<string> GetInCorrectDiscoveryEndpoints(Entry[] allEntries)
{
foreach (var peer in Response.table.nodes)
{
var expected = GetExpectedDiscoveryEndpoint(allEntries, peer);
if (expected != peer.address)
{
yield return $"Node:{Node.GetName()} has incorrect peer table entry. Was: '{peer.address}', expected: '{expected}'";
}
}
}
private static string GetExpectedDiscoveryEndpoint(Entry[] allEntries, CodexDebugTableNodeResponse node)
{
var peer = allEntries.SingleOrDefault(e => e.Response.table.localNode.peerId == node.peerId);
if (peer == null) return $"peerId: {node.peerId} is not known.";
var n = (OnlineCodexNode)peer.Node;
var ip = n.CodexAccess.Container.Pod.Ip;
var discPort = n.CodexAccess.Container.Recipe.GetPortByTag(CodexContainerRecipe.DiscoveryPortTag);
return $"{ip}:{discPort.Number}";
}
}
2023-05-12 08:48:12 +00:00
public class Pair
{
private readonly 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()
{
ApplyRandomDelay();
aToBTime = Measure(() => AKnowsB = Knows(A, B));
bToATime = Measure(() => BKnowsA = Knows(B, A));
2023-05-12 08:48:12 +00:00
}
public string GetMessage()
{
return GetResultMessage() + GetTimePostfix();
}
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.";
}
private string GetTimePostfix()
2023-05-12 08:48:12 +00:00
{
var aName = A.Response.id;
var bName = B.Response.id;
2023-05-12 08:48:12 +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
{
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
{
}
return false;
}
2023-05-12 08:48:12 +00:00
}
}
}
}