2023-05-10 07:55:36 +00:00
|
|
|
|
using DistTestCore.Codex;
|
|
|
|
|
using NUnit.Framework;
|
2023-05-12 08:48:12 +00:00
|
|
|
|
using Utils;
|
2023-05-10 07:55:36 +00:00
|
|
|
|
|
2023-05-29 07:13:38 +00:00
|
|
|
|
namespace DistTestCore.Helpers
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2023-05-29 07:13:38 +00:00
|
|
|
|
public class PeerConnectionTestHelpers
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2023-07-18 12:26:21 +00:00
|
|
|
|
private static string Nl = Environment.NewLine;
|
2023-05-29 07:13:38 +00:00
|
|
|
|
private readonly Random random = new Random();
|
|
|
|
|
private readonly DistTest test;
|
2023-05-18 08:42:04 +00:00
|
|
|
|
|
2023-05-29 07:13:38 +00:00
|
|
|
|
public PeerConnectionTestHelpers(DistTest test)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2023-05-29 07:13:38 +00:00
|
|
|
|
this.test = test;
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-29 07:13:38 +00:00
|
|
|
|
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-05-29 07:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 12:36:37 +00:00
|
|
|
|
private void AssertFullyConnected(IOnlineCodexNode[] nodes)
|
2023-05-10 07:55:36 +00:00
|
|
|
|
{
|
2023-05-31 11:15:41 +00:00
|
|
|
|
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
|
|
|
|
{
|
2023-05-29 07:13:38 +00:00
|
|
|
|
CheckAndRemoveSuccessful(pairs);
|
2023-05-12 08:48:12 +00:00
|
|
|
|
});
|
2023-05-29 07:13:38 +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())));
|
2023-05-31 11:15:41 +00:00
|
|
|
|
}
|
|
|
|
|
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-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-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-05-12 07:11:05 +00:00
|
|
|
|
|
2023-07-18 12:26:21 +00:00
|
|
|
|
Time.Sleep(TimeSpan.FromSeconds(2));
|
2023-05-12 08:48:12 +00:00
|
|
|
|
}
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-29 07:13:38 +00:00
|
|
|
|
private void CheckAndRemoveSuccessful(List<Pair> pairs)
|
2023-05-12 07:11:05 +00:00
|
|
|
|
{
|
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(() =>
|
2023-05-29 07:13:38 +00:00
|
|
|
|
{
|
|
|
|
|
ApplyRandomDelay();
|
|
|
|
|
p.Check();
|
|
|
|
|
})).ToArray();
|
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
Task.WaitAll(checkTasks);
|
2023-05-12 07:11:05 +00:00
|
|
|
|
|
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 07:11:05 +00:00
|
|
|
|
{
|
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-05-18 08:42:04 +00:00
|
|
|
|
}
|
2023-05-12 07:11:05 +00:00
|
|
|
|
}
|
2023-07-18 12:26:21 +00:00
|
|
|
|
test.Log($"Connections successful:{Nl}{string.Join(Nl, pairDetails)}");
|
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-29 06:35:46 +00:00
|
|
|
|
var entries = nodes.Select(n => new Entry(n)).ToArray();
|
|
|
|
|
var incorrectDiscoveryEndpoints = entries.SelectMany(e => e.GetInCorrectDiscoveryEndpoints(entries)).ToArray();
|
2023-05-29 07:13:38 +00:00
|
|
|
|
|
2023-05-29 06:35:46 +00:00
|
|
|
|
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));
|
2023-05-29 06:35:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-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
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-29 07:13:38 +00:00
|
|
|
|
private void ApplyRandomDelay()
|
|
|
|
|
{
|
|
|
|
|
// Calling all the nodes all at the same time is not exactly nice.
|
2023-05-31 11:15:41 +00:00
|
|
|
|
Time.Sleep(TimeSpan.FromMicroseconds(random.Next(10, 1000)));
|
2023-05-29 07:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
2023-05-29 07:13:38 +00:00
|
|
|
|
public IOnlineCodexNode Node { get; }
|
2023-05-11 10:44:53 +00:00
|
|
|
|
public CodexDebugResponse Response { get; }
|
2023-05-29 06:35:46 +00:00
|
|
|
|
|
|
|
|
|
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}'";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-14 08:18:37 +00:00
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
if (Response == null || string.IsNullOrEmpty(Response.id)) return "UNKNOWN";
|
|
|
|
|
return Response.id;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-29 06:35:46 +00:00
|
|
|
|
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;
|
2023-06-02 08:04:07 +00:00
|
|
|
|
var ip = n.CodexAccess.Container.Pod.PodInfo.Ip;
|
2023-05-29 06:35:46 +00:00
|
|
|
|
var discPort = n.CodexAccess.Container.Recipe.GetPortByTag(CodexContainerRecipe.DiscoveryPortTag);
|
|
|
|
|
return $"{ip}:{discPort.Number}";
|
|
|
|
|
}
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
2023-05-12 08:48:12 +00:00
|
|
|
|
|
2023-05-31 11:15:41 +00:00
|
|
|
|
public enum PeerConnectionState
|
|
|
|
|
{
|
|
|
|
|
Unknown,
|
|
|
|
|
Connection,
|
|
|
|
|
NoConnection,
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 08:48:12 +00:00
|
|
|
|
public class Pair
|
|
|
|
|
{
|
2023-05-18 08:42:04 +00:00
|
|
|
|
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; }
|
2023-05-31 11:15:41 +00:00
|
|
|
|
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()
|
|
|
|
|
{
|
2023-05-18 08:42:04 +00:00
|
|
|
|
aToBTime = Measure(() => AKnowsB = Knows(A, B));
|
|
|
|
|
bToATime = Measure(() => BKnowsA = Knows(B, A));
|
2023-05-12 08:48:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-14 08:18:37 +00:00
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
2023-07-18 12:26:21 +00:00
|
|
|
|
return $"[{string.Join(",", GetResultMessages())}]";
|
2023-07-14 08:18:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-18 12:26:21 +00:00
|
|
|
|
public string[] GetResultMessages()
|
2023-05-12 08:48:12 +00:00
|
|
|
|
{
|
2023-07-14 08:18:37 +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)"
|
|
|
|
|
};
|
2023-05-18 08:42:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TimeSpan Measure(Action action)
|
|
|
|
|
{
|
|
|
|
|
var start = DateTime.UtcNow;
|
|
|
|
|
action();
|
|
|
|
|
return DateTime.UtcNow - start;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-31 11:15:41 +00:00
|
|
|
|
private PeerConnectionState Knows(Entry a, Entry b)
|
2023-05-18 08:42:04 +00:00
|
|
|
|
{
|
|
|
|
|
lock (a)
|
2023-05-12 08:48:12 +00:00
|
|
|
|
{
|
2023-07-18 12:26:21 +00:00
|
|
|
|
Thread.Sleep(10);
|
2023-05-18 08:42:04 +00:00
|
|
|
|
var peerId = b.Response.id;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2023-07-17 13:21:10 +00:00
|
|
|
|
var response = a.Node.GetDebugPeer(peerId);
|
2023-05-31 11:15:41 +00:00
|
|
|
|
if (!response.IsPeerFound)
|
|
|
|
|
{
|
|
|
|
|
return PeerConnectionState.NoConnection;
|
|
|
|
|
}
|
2023-05-18 08:42:04 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(response.peerId) && response.addresses.Any())
|
|
|
|
|
{
|
2023-05-31 11:15:41 +00:00
|
|
|
|
return PeerConnectionState.Connection;
|
2023-05-18 08:42:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
2023-05-12 08:48:12 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-31 11:15:41 +00:00
|
|
|
|
// Didn't get a conclusive answer. Try again later.
|
|
|
|
|
return PeerConnectionState.Unknown;
|
2023-05-18 08:42:04 +00:00
|
|
|
|
}
|
2023-05-12 08:48:12 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-10 07:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|