cs-codex-dist-tests/DistTestCore/Helpers/PeerConnectionTestHelpers.cs

252 lines
8.3 KiB
C#
Raw Normal View History

using DistTestCore.Codex;
using NUnit.Framework;
2023-05-12 08:48:12 +00:00
using Utils;
namespace DistTestCore.Helpers
{
public class PeerConnectionTestHelpers
{
2023-07-18 12:26:21 +00:00
private static string Nl = Environment.NewLine;
private readonly Random random = new Random();
private readonly DistTest test;
public PeerConnectionTestHelpers(DistTest test)
{
this.test = test;
}
public void AssertFullyConnected(IEnumerable<IOnlineCodexNode> nodes)
{
2023-06-06 12:36:37 +00:00
var n = nodes.ToArray();
AssertFullyConnected(n);
for (int i = 0; i < 5; i++)
{
Time.Sleep(TimeSpan.FromSeconds(30));
AssertFullyConnected(n);
}
}
2023-06-06 12:36:37 +00:00
private void AssertFullyConnected(IOnlineCodexNode[] nodes)
{
test.Log($"Asserting peers are fully-connected for nodes: '{string.Join(",", nodes.Select(n => n.GetName()))}'...");
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
{
CheckAndRemoveSuccessful(pairs);
2023-05-12 08:48:12 +00:00
});
2023-05-12 08:48:12 +00:00
if (pairs.Any())
{
2023-07-18 12:26:21 +00:00
var pairDetails = string.Join(Nl, pairs.SelectMany(p => p.GetResultMessages()));
test.Log($"Connections failed:{Nl}{pairDetails}");
Assert.Fail(string.Join(Nl, pairs.SelectMany(p => p.GetResultMessages())));
}
else
{
test.Log($"Success! Peers are fully-connected: {string.Join(",", nodes.Select(n => n.GetName()))}");
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)
{
2023-07-18 12:26:21 +00:00
var timeout = DateTime.UtcNow + TimeSpan.FromMinutes(5);
while (pairs.Any(p => p.Inconclusive) && timeout > DateTime.UtcNow)
2023-05-12 08:48:12 +00:00
{
action();
2023-07-18 12:26:21 +00:00
Time.Sleep(TimeSpan.FromSeconds(2));
2023-05-12 08:48:12 +00:00
}
}
private void CheckAndRemoveSuccessful(List<Pair> pairs)
{
2023-07-18 12:26:21 +00:00
// For large sets, don't try and do all of them at once.
var checkTasks = pairs.Take(20).Select(p => Task.Run(() =>
{
ApplyRandomDelay();
p.Check();
})).ToArray();
2023-05-12 08:48:12 +00:00
Task.WaitAll(checkTasks);
2023-07-18 12:26:21 +00:00
var pairDetails = new List<string>();
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)
{
2023-07-18 12:26:21 +00:00
pairDetails.AddRange(pair.GetResultMessages());
2023-05-12 08:48:12 +00:00
pairs.Remove(pair);
}
}
2023-07-18 12:26:21 +00:00
test.Log($"Connections successful:{Nl}{string.Join(Nl, pairDetails)}");
}
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: " +
2023-07-18 12:26:21 +00:00
string.Join(Nl, 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
}
}
}
private void ApplyRandomDelay()
{
// Calling all the nodes all at the same time is not exactly nice.
Time.Sleep(TimeSpan.FromMicroseconds(random.Next(10, 1000)));
}
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; }
2023-05-11 10:44:53 +00:00
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}'";
}
}
}
public override string ToString()
{
if (Response == null || string.IsNullOrEmpty(Response.id)) return "UNKNOWN";
return Response.id;
}
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.PodInfo.Ip;
var discPort = n.CodexAccess.Container.Recipe.GetPortByTag(CodexContainerRecipe.DiscoveryPortTag);
return $"{ip}:{discPort.Number}";
}
}
2023-05-12 08:48:12 +00:00
public enum PeerConnectionState
{
Unknown,
Connection,
NoConnection,
}
2023-05-12 08:48:12 +00:00
public class Pair
{
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 PeerConnectionState AKnowsB { get; private set; }
public PeerConnectionState BKnowsA { get; private set; }
public bool Success { get { return AKnowsB == PeerConnectionState.Connection && BKnowsA == PeerConnectionState.Connection; } }
2023-07-18 12:26:21 +00:00
public bool Inconclusive { get { return AKnowsB == PeerConnectionState.Unknown || BKnowsA == PeerConnectionState.Unknown; } }
2023-05-12 08:48:12 +00:00
public void Check()
{
aToBTime = Measure(() => AKnowsB = Knows(A, B));
bToATime = Measure(() => BKnowsA = Knows(B, A));
2023-05-12 08:48:12 +00:00
}
public override string ToString()
{
2023-07-18 12:26:21 +00:00
return $"[{string.Join(",", GetResultMessages())}]";
}
2023-07-18 12:26:21 +00:00
public string[] GetResultMessages()
2023-05-12 08:48:12 +00:00
{
var aName = A.ToString();
var bName = B.ToString();
2023-05-12 08:48:12 +00:00
2023-07-18 12:26:21 +00:00
return new[]
2023-05-12 08:48:12 +00:00
{
2023-07-18 12:26:21 +00:00
$"[{aName} --> {bName}] = {AKnowsB} ({aToBTime.TotalSeconds} seconds)",
$"[{aName} <-- {bName}] = {BKnowsA} ({bToATime.TotalSeconds} seconds)"
};
}
private static TimeSpan Measure(Action action)
{
var start = DateTime.UtcNow;
action();
return DateTime.UtcNow - start;
}
private PeerConnectionState Knows(Entry a, Entry b)
{
lock (a)
2023-05-12 08:48:12 +00:00
{
2023-07-18 12:26:21 +00:00
Thread.Sleep(10);
var peerId = b.Response.id;
try
{
var response = a.Node.GetDebugPeer(peerId);
if (!response.IsPeerFound)
{
return PeerConnectionState.NoConnection;
}
if (!string.IsNullOrEmpty(response.peerId) && response.addresses.Any())
{
return PeerConnectionState.Connection;
}
}
catch
2023-05-12 08:48:12 +00:00
{
}
// Didn't get a conclusive answer. Try again later.
return PeerConnectionState.Unknown;
}
2023-05-12 08:48:12 +00:00
}
}
}
}