Merge branch 'feature/location'
This commit is contained in:
commit
8c16ae3385
|
@ -1,4 +1,5 @@
|
||||||
using k8s.Models;
|
using CodexDistTestCore.Config;
|
||||||
|
using k8s.Models;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
||||||
namespace CodexDistTestCore
|
namespace CodexDistTestCore
|
||||||
|
@ -11,10 +12,12 @@ namespace CodexDistTestCore
|
||||||
|
|
||||||
public class CodexNodeGroup : ICodexNodeGroup
|
public class CodexNodeGroup : ICodexNodeGroup
|
||||||
{
|
{
|
||||||
|
private readonly TestLog log;
|
||||||
private readonly IK8sManager k8SManager;
|
private readonly IK8sManager k8SManager;
|
||||||
|
|
||||||
public CodexNodeGroup(int orderNumber, OfflineCodexNodes origin, IK8sManager k8SManager, OnlineCodexNode[] nodes)
|
public CodexNodeGroup(TestLog log, int orderNumber, OfflineCodexNodes origin, IK8sManager k8SManager, OnlineCodexNode[] nodes)
|
||||||
{
|
{
|
||||||
|
this.log = log;
|
||||||
OrderNumber = orderNumber;
|
OrderNumber = orderNumber;
|
||||||
Origin = origin;
|
Origin = origin;
|
||||||
this.k8SManager = k8SManager;
|
this.k8SManager = k8SManager;
|
||||||
|
@ -63,7 +66,7 @@ namespace CodexDistTestCore
|
||||||
return new V1ObjectMeta
|
return new V1ObjectMeta
|
||||||
{
|
{
|
||||||
Name = "codex-test-entrypoint-" + OrderNumber,
|
Name = "codex-test-entrypoint-" + OrderNumber,
|
||||||
NamespaceProperty = K8sOperations.K8sNamespace
|
NamespaceProperty = K8sCluster.K8sNamespace
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,10 +75,17 @@ namespace CodexDistTestCore
|
||||||
return new V1ObjectMeta
|
return new V1ObjectMeta
|
||||||
{
|
{
|
||||||
Name = "codex-test-node-" + OrderNumber,
|
Name = "codex-test-node-" + OrderNumber,
|
||||||
NamespaceProperty = K8sOperations.K8sNamespace
|
NamespaceProperty = K8sCluster.K8sNamespace
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CodexNodeLog DownloadLog(IOnlineCodexNode node)
|
||||||
|
{
|
||||||
|
var logDownloader = new PodLogDownloader(log, k8SManager);
|
||||||
|
var n = (OnlineCodexNode)node;
|
||||||
|
return logDownloader.DownloadLog(n);
|
||||||
|
}
|
||||||
|
|
||||||
public Dictionary<string, string> GetSelector()
|
public Dictionary<string, string> GetSelector()
|
||||||
{
|
{
|
||||||
return new Dictionary<string, string> { { "codex-test-node", "dist-test-" + OrderNumber } };
|
return new Dictionary<string, string> { { "codex-test-node", "dist-test-" + OrderNumber } };
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace CodexDistTestCore
|
||||||
|
{
|
||||||
|
public class CodexNodeLog
|
||||||
|
{
|
||||||
|
private readonly LogFile logFile;
|
||||||
|
|
||||||
|
public CodexNodeLog(LogFile logFile)
|
||||||
|
{
|
||||||
|
this.logFile = logFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AssertLogContains(string expectedString)
|
||||||
|
{
|
||||||
|
using var file = File.OpenRead(logFile.FullFilename);
|
||||||
|
using var streamReader = new StreamReader(file);
|
||||||
|
|
||||||
|
var line = streamReader.ReadLine();
|
||||||
|
while (line != null)
|
||||||
|
{
|
||||||
|
if (line.Contains(expectedString)) return;
|
||||||
|
line = streamReader.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Fail($"Unable to find string '{expectedString}' in CodexNode log file {logFile.FilenameWithoutPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
using k8s.Models;
|
using k8s.Models;
|
||||||
|
|
||||||
namespace CodexDistTestCore
|
namespace CodexDistTestCore.Config
|
||||||
{
|
{
|
||||||
public class CodexDockerImage
|
public class CodexDockerImage
|
||||||
{
|
{
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace CodexDistTestCore.Config
|
||||||
|
{
|
||||||
|
public class FileManagerConfig
|
||||||
|
{
|
||||||
|
public const string Folder = "TestDataFiles";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
using k8s;
|
||||||
|
|
||||||
|
namespace CodexDistTestCore.Config
|
||||||
|
{
|
||||||
|
public class K8sCluster
|
||||||
|
{
|
||||||
|
public const string K8sNamespace = "codex-test-namespace";
|
||||||
|
private const string KubeConfigFile = "C:\\kube\\config";
|
||||||
|
private readonly Dictionary<Location, string> K8sNodeLocationMap = new Dictionary<Location, string>
|
||||||
|
{
|
||||||
|
{ Location.BensLaptop, "worker01" },
|
||||||
|
{ Location.BensOldGamingMachine, "worker02" },
|
||||||
|
};
|
||||||
|
|
||||||
|
private KubernetesClientConfiguration? config;
|
||||||
|
|
||||||
|
public KubernetesClientConfiguration GetK8sClientConfig()
|
||||||
|
{
|
||||||
|
if (config != null) return config;
|
||||||
|
config = KubernetesClientConfiguration.BuildConfigFromConfigFile(KubeConfigFile);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetIp()
|
||||||
|
{
|
||||||
|
var c = GetK8sClientConfig();
|
||||||
|
|
||||||
|
var host = c.Host.Replace("https://", "");
|
||||||
|
|
||||||
|
return host.Substring(0, host.IndexOf(':'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetNodeLabelForLocation(Location location)
|
||||||
|
{
|
||||||
|
if (location == Location.Unspecified) return string.Empty;
|
||||||
|
return K8sNodeLocationMap[location];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace CodexDistTestCore.Config
|
||||||
|
{
|
||||||
|
public class LogConfig
|
||||||
|
{
|
||||||
|
public const string LogRoot = "D:/CodexTestLogs";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using NUnit.Framework;
|
using CodexDistTestCore.Config;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace CodexDistTestCore
|
namespace CodexDistTestCore
|
||||||
{
|
{
|
||||||
|
@ -41,7 +42,10 @@ namespace CodexDistTestCore
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var dockerImage = new CodexDockerImage();
|
||||||
log = new TestLog();
|
log = new TestLog();
|
||||||
|
log.Log($"Using docker image '{dockerImage.GetImageTag()}'");
|
||||||
|
|
||||||
fileManager = new FileManager(log);
|
fileManager = new FileManager(log);
|
||||||
k8sManager = new K8sManager(log, fileManager);
|
k8sManager = new K8sManager(log, fileManager);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +56,8 @@ namespace CodexDistTestCore
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
log.EndTest(k8sManager);
|
log.EndTest();
|
||||||
|
IncludeLogsOnTestFailure();
|
||||||
k8sManager.DeleteAllResources();
|
k8sManager.DeleteAllResources();
|
||||||
fileManager.DeleteAllTestFiles();
|
fileManager.DeleteAllTestFiles();
|
||||||
}
|
}
|
||||||
|
@ -72,6 +77,39 @@ namespace CodexDistTestCore
|
||||||
{
|
{
|
||||||
return new OfflineCodexNodes(k8sManager, numberOfNodes);
|
return new OfflineCodexNodes(k8sManager, numberOfNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void IncludeLogsOnTestFailure()
|
||||||
|
{
|
||||||
|
var result = TestContext.CurrentContext.Result;
|
||||||
|
if (result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Failed)
|
||||||
|
{
|
||||||
|
if (IsDownloadingLogsEnabled())
|
||||||
|
{
|
||||||
|
log.Log("Downloading all CodexNode logs because of test failure...");
|
||||||
|
k8sManager.ForEachOnlineGroup(DownloadLogs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.Log("Skipping download of all CodexNode logs due to [DontDownloadLogsOnFailure] attribute.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DownloadLogs(CodexNodeGroup group)
|
||||||
|
{
|
||||||
|
foreach (var node in group)
|
||||||
|
{
|
||||||
|
var downloader = new PodLogDownloader(log, k8sManager);
|
||||||
|
var n = (OnlineCodexNode)node;
|
||||||
|
downloader.DownloadLog(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDownloadingLogsEnabled()
|
||||||
|
{
|
||||||
|
var testProperties = TestContext.CurrentContext.Test.Properties;
|
||||||
|
return !testProperties.ContainsKey(PodLogDownloader.DontDownloadLogsOnFailureKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GlobalTestFailure
|
public static class GlobalTestFailure
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using NUnit.Framework;
|
using CodexDistTestCore.Config;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace CodexDistTestCore
|
namespace CodexDistTestCore
|
||||||
{
|
{
|
||||||
|
@ -12,20 +13,19 @@ namespace CodexDistTestCore
|
||||||
public class FileManager : IFileManager
|
public class FileManager : IFileManager
|
||||||
{
|
{
|
||||||
public const int ChunkSize = 1024 * 1024;
|
public const int ChunkSize = 1024 * 1024;
|
||||||
private const string Folder = "TestDataFiles";
|
|
||||||
private readonly Random random = new Random();
|
private readonly Random random = new Random();
|
||||||
private readonly List<TestFile> activeFiles = new List<TestFile>();
|
private readonly List<TestFile> activeFiles = new List<TestFile>();
|
||||||
private readonly TestLog log;
|
private readonly TestLog log;
|
||||||
|
|
||||||
public FileManager(TestLog log)
|
public FileManager(TestLog log)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(Folder)) Directory.CreateDirectory(Folder);
|
if (!Directory.Exists(FileManagerConfig.Folder)) Directory.CreateDirectory(FileManagerConfig.Folder);
|
||||||
this.log = log;
|
this.log = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestFile CreateEmptyTestFile()
|
public TestFile CreateEmptyTestFile()
|
||||||
{
|
{
|
||||||
var result = new TestFile(Path.Combine(Folder, Guid.NewGuid().ToString() + "_test.bin"));
|
var result = new TestFile(Path.Combine(FileManagerConfig.Folder, Guid.NewGuid().ToString() + "_test.bin"));
|
||||||
File.Create(result.Filename).Close();
|
File.Create(result.Filename).Close();
|
||||||
activeFiles.Add(result);
|
activeFiles.Add(result);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -4,12 +4,13 @@
|
||||||
{
|
{
|
||||||
ICodexNodeGroup BringOnline(OfflineCodexNodes node);
|
ICodexNodeGroup BringOnline(OfflineCodexNodes node);
|
||||||
IOfflineCodexNodes BringOffline(ICodexNodeGroup node);
|
IOfflineCodexNodes BringOffline(ICodexNodeGroup node);
|
||||||
|
void FetchPodLog(OnlineCodexNode node, IPodLogHandler logHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class K8sManager : IK8sManager
|
public class K8sManager : IK8sManager
|
||||||
{
|
{
|
||||||
private readonly CodexGroupNumberSource codexGroupNumberSource = new CodexGroupNumberSource();
|
private readonly CodexGroupNumberSource codexGroupNumberSource = new CodexGroupNumberSource();
|
||||||
private readonly List<CodexNodeGroup> onlineCodexNodes = new List<CodexNodeGroup>();
|
private readonly List<CodexNodeGroup> onlineCodexNodeGroups = new List<CodexNodeGroup>();
|
||||||
private readonly KnownK8sPods knownPods = new KnownK8sPods();
|
private readonly KnownK8sPods knownPods = new KnownK8sPods();
|
||||||
private readonly TestLog log;
|
private readonly TestLog log;
|
||||||
private readonly IFileManager fileManager;
|
private readonly IFileManager fileManager;
|
||||||
|
@ -47,17 +48,22 @@
|
||||||
K8s(k => k.DeleteAllResources());
|
K8s(k => k.DeleteAllResources());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FetchAllPodsLogs(IPodLogsHandler logHandler)
|
public void ForEachOnlineGroup(Action<CodexNodeGroup> action)
|
||||||
{
|
{
|
||||||
K8s(k => k.FetchAllPodsLogs(onlineCodexNodes.ToArray(), logHandler));
|
foreach (var group in onlineCodexNodeGroups) action(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FetchPodLog(OnlineCodexNode node, IPodLogHandler logHandler)
|
||||||
|
{
|
||||||
|
K8s(k => k.FetchPodLog(node, logHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
private CodexNodeGroup CreateOnlineCodexNodes(OfflineCodexNodes offline)
|
private CodexNodeGroup CreateOnlineCodexNodes(OfflineCodexNodes offline)
|
||||||
{
|
{
|
||||||
var containers = CreateContainers(offline.NumberOfNodes);
|
var containers = CreateContainers(offline.NumberOfNodes);
|
||||||
var online = containers.Select(c => new OnlineCodexNode(log, fileManager, c)).ToArray();
|
var online = containers.Select(c => new OnlineCodexNode(log, fileManager, c)).ToArray();
|
||||||
var result = new CodexNodeGroup(codexGroupNumberSource.GetNextCodexNodeGroupNumber(), offline, this, online);
|
var result = new CodexNodeGroup(log, codexGroupNumberSource.GetNextCodexNodeGroupNumber(), offline, this, online);
|
||||||
onlineCodexNodes.Add(result);
|
onlineCodexNodeGroups.Add(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +78,7 @@
|
||||||
private CodexNodeGroup GetAndRemoveActiveNodeFor(ICodexNodeGroup node)
|
private CodexNodeGroup GetAndRemoveActiveNodeFor(ICodexNodeGroup node)
|
||||||
{
|
{
|
||||||
var n = (CodexNodeGroup)node;
|
var n = (CodexNodeGroup)node;
|
||||||
onlineCodexNodes.Remove(n);
|
onlineCodexNodeGroups.Remove(n);
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using k8s;
|
using CodexDistTestCore.Config;
|
||||||
|
using k8s;
|
||||||
using k8s.Models;
|
using k8s.Models;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
@ -6,9 +7,8 @@ namespace CodexDistTestCore
|
||||||
{
|
{
|
||||||
public class K8sOperations
|
public class K8sOperations
|
||||||
{
|
{
|
||||||
public const string K8sNamespace = "codex-test-namespace";
|
|
||||||
|
|
||||||
private readonly CodexDockerImage dockerImage = new CodexDockerImage();
|
private readonly CodexDockerImage dockerImage = new CodexDockerImage();
|
||||||
|
private readonly K8sCluster k8sCluster = new K8sCluster();
|
||||||
private readonly Kubernetes client;
|
private readonly Kubernetes client;
|
||||||
private readonly KnownK8sPods knownPods;
|
private readonly KnownK8sPods knownPods;
|
||||||
|
|
||||||
|
@ -16,9 +16,7 @@ namespace CodexDistTestCore
|
||||||
{
|
{
|
||||||
this.knownPods = knownPods;
|
this.knownPods = knownPods;
|
||||||
|
|
||||||
// todo: If the default KubeConfig file does not suffice, change it here:
|
client = new Kubernetes(k8sCluster.GetK8sClientConfig());
|
||||||
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
|
|
||||||
client = new Kubernetes(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
public void Close()
|
||||||
|
@ -53,25 +51,10 @@ namespace CodexDistTestCore
|
||||||
WaitUntilNamespaceDeleted();
|
WaitUntilNamespaceDeleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FetchAllPodsLogs(CodexNodeGroup[] onlines, IPodLogsHandler logHandler)
|
public void FetchPodLog(OnlineCodexNode node, IPodLogHandler logHandler)
|
||||||
{
|
{
|
||||||
var logNumberSource = new NumberSource(0);
|
var stream = client.ReadNamespacedPodLog(node.Group.PodInfo!.Name, K8sNamespace, node.Container.Name);
|
||||||
foreach (var online in onlines)
|
logHandler.Log(stream);
|
||||||
{
|
|
||||||
foreach (var node in online)
|
|
||||||
{
|
|
||||||
WritePodLogs(online, node, logHandler, logNumberSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WritePodLogs(CodexNodeGroup online, IOnlineCodexNode node, IPodLogsHandler logHandler, NumberSource logNumberSource)
|
|
||||||
{
|
|
||||||
var n = (OnlineCodexNode)node;
|
|
||||||
var nodeDescription = $"{online.Describe()} contains {n.GetName()}";
|
|
||||||
|
|
||||||
var stream = client.ReadNamespacedPodLog(online.PodInfo!.Name, K8sNamespace, n.Container.Name);
|
|
||||||
logHandler.Log(logNumberSource.GetNextNumber(), nodeDescription, stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FetchPodInfo(CodexNodeGroup online)
|
private void FetchPodInfo(CodexNodeGroup online)
|
||||||
|
@ -208,6 +191,7 @@ namespace CodexDistTestCore
|
||||||
},
|
},
|
||||||
Spec = new V1PodSpec
|
Spec = new V1PodSpec
|
||||||
{
|
{
|
||||||
|
NodeSelector = CreateNodeSelector(offline),
|
||||||
Containers = CreateDeploymentContainers(online, offline)
|
Containers = CreateDeploymentContainers(online, offline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,6 +201,16 @@ namespace CodexDistTestCore
|
||||||
online.Deployment = client.CreateNamespacedDeployment(deploymentSpec, K8sNamespace);
|
online.Deployment = client.CreateNamespacedDeployment(deploymentSpec, K8sNamespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IDictionary<string, string> CreateNodeSelector(OfflineCodexNodes offline)
|
||||||
|
{
|
||||||
|
if (offline.Location == Location.Unspecified) return new Dictionary<string, string>();
|
||||||
|
|
||||||
|
return new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "codex-test-location", k8sCluster.GetNodeLabelForLocation(offline.Location) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private List<V1Container> CreateDeploymentContainers(CodexNodeGroup online, OfflineCodexNodes offline)
|
private List<V1Container> CreateDeploymentContainers(CodexNodeGroup online, OfflineCodexNodes offline)
|
||||||
{
|
{
|
||||||
var result = new List<V1Container>();
|
var result = new List<V1Container>();
|
||||||
|
@ -276,6 +270,11 @@ namespace CodexDistTestCore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string K8sNamespace
|
||||||
|
{
|
||||||
|
get { return K8sCluster.K8sNamespace; }
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private bool IsTestNamespaceOnline()
|
private bool IsTestNamespaceOnline()
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
{
|
{
|
||||||
public interface IOfflineCodexNodes
|
public interface IOfflineCodexNodes
|
||||||
{
|
{
|
||||||
|
IOfflineCodexNodes At(Location location);
|
||||||
IOfflineCodexNodes WithLogLevel(CodexLogLevel level);
|
IOfflineCodexNodes WithLogLevel(CodexLogLevel level);
|
||||||
IOfflineCodexNodes WithBootstrapNode(IOnlineCodexNode node);
|
IOfflineCodexNodes WithBootstrapNode(IOnlineCodexNode node);
|
||||||
IOfflineCodexNodes WithStorageQuota(ByteSize storageQuota);
|
IOfflineCodexNodes WithStorageQuota(ByteSize storageQuota);
|
||||||
|
@ -17,11 +18,19 @@
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Location
|
||||||
|
{
|
||||||
|
Unspecified,
|
||||||
|
BensLaptop,
|
||||||
|
BensOldGamingMachine,
|
||||||
|
}
|
||||||
|
|
||||||
public class OfflineCodexNodes : IOfflineCodexNodes
|
public class OfflineCodexNodes : IOfflineCodexNodes
|
||||||
{
|
{
|
||||||
private readonly IK8sManager k8SManager;
|
private readonly IK8sManager k8SManager;
|
||||||
|
|
||||||
public int NumberOfNodes { get; }
|
public int NumberOfNodes { get; }
|
||||||
|
public Location Location { get; private set; }
|
||||||
public CodexLogLevel? LogLevel { get; private set; }
|
public CodexLogLevel? LogLevel { get; private set; }
|
||||||
public IOnlineCodexNode? BootstrapNode { get; private set; }
|
public IOnlineCodexNode? BootstrapNode { get; private set; }
|
||||||
public ByteSize? StorageQuota { get; private set; }
|
public ByteSize? StorageQuota { get; private set; }
|
||||||
|
@ -30,6 +39,7 @@
|
||||||
{
|
{
|
||||||
this.k8SManager = k8SManager;
|
this.k8SManager = k8SManager;
|
||||||
NumberOfNodes = numberOfNodes;
|
NumberOfNodes = numberOfNodes;
|
||||||
|
Location = Location.Unspecified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICodexNodeGroup BringOnline()
|
public ICodexNodeGroup BringOnline()
|
||||||
|
@ -37,6 +47,12 @@
|
||||||
return k8SManager.BringOnline(this);
|
return k8SManager.BringOnline(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IOfflineCodexNodes At(Location location)
|
||||||
|
{
|
||||||
|
Location = location;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public IOfflineCodexNodes WithBootstrapNode(IOnlineCodexNode node)
|
public IOfflineCodexNodes WithBootstrapNode(IOnlineCodexNode node)
|
||||||
{
|
{
|
||||||
BootstrapNode = node;
|
BootstrapNode = node;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using NUnit.Framework;
|
using CodexDistTestCore.Config;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace CodexDistTestCore
|
namespace CodexDistTestCore
|
||||||
{
|
{
|
||||||
|
@ -8,6 +9,7 @@ namespace CodexDistTestCore
|
||||||
ContentId UploadFile(TestFile file);
|
ContentId UploadFile(TestFile file);
|
||||||
TestFile? DownloadContent(ContentId contentId);
|
TestFile? DownloadContent(ContentId contentId);
|
||||||
void ConnectToPeer(IOnlineCodexNode node);
|
void ConnectToPeer(IOnlineCodexNode node);
|
||||||
|
CodexNodeLog DownloadLog();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OnlineCodexNode : IOnlineCodexNode
|
public class OnlineCodexNode : IOnlineCodexNode
|
||||||
|
@ -15,6 +17,7 @@ namespace CodexDistTestCore
|
||||||
private const string SuccessfullyConnectedMessage = "Successfully connected to peer";
|
private const string SuccessfullyConnectedMessage = "Successfully connected to peer";
|
||||||
private const string UploadFailedMessage = "Unable to store block";
|
private const string UploadFailedMessage = "Unable to store block";
|
||||||
|
|
||||||
|
private readonly K8sCluster k8sCluster = new K8sCluster();
|
||||||
private readonly TestLog log;
|
private readonly TestLog log;
|
||||||
private readonly IFileManager fileManager;
|
private readonly IFileManager fileManager;
|
||||||
|
|
||||||
|
@ -77,6 +80,16 @@ namespace CodexDistTestCore
|
||||||
Log($"Successfully connected to peer {peer.GetName()}.");
|
Log($"Successfully connected to peer {peer.GetName()}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CodexNodeLog DownloadLog()
|
||||||
|
{
|
||||||
|
return Group.DownloadLog(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Describe()
|
||||||
|
{
|
||||||
|
return $"{Group.Describe()} contains {GetName()}";
|
||||||
|
}
|
||||||
|
|
||||||
private string GetPeerMultiAddress(OnlineCodexNode peer, CodexDebugResponse peerInfo)
|
private string GetPeerMultiAddress(OnlineCodexNode peer, CodexDebugResponse peerInfo)
|
||||||
{
|
{
|
||||||
var multiAddress = peerInfo.addrs.First();
|
var multiAddress = peerInfo.addrs.First();
|
||||||
|
@ -101,7 +114,7 @@ namespace CodexDistTestCore
|
||||||
|
|
||||||
private Http Http()
|
private Http Http()
|
||||||
{
|
{
|
||||||
return new Http(ip: "127.0.0.1", port: Container.ServicePort, baseUrl: "/api/codex/v1");
|
return new Http(ip: k8sCluster.GetIp(), port: Container.ServicePort, baseUrl: "/api/codex/v1");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Log(string msg)
|
private void Log(string msg)
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace CodexDistTestCore
|
||||||
|
{
|
||||||
|
public interface IPodLogHandler
|
||||||
|
{
|
||||||
|
void Log(Stream log);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||||
|
public class DontDownloadLogsOnFailureAttribute : PropertyAttribute
|
||||||
|
{
|
||||||
|
public DontDownloadLogsOnFailureAttribute()
|
||||||
|
: base(Timing.UseLongTimeoutsKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PodLogDownloader
|
||||||
|
{
|
||||||
|
public const string DontDownloadLogsOnFailureKey = "DontDownloadLogsOnFailure";
|
||||||
|
|
||||||
|
private readonly TestLog log;
|
||||||
|
private readonly IK8sManager k8SManager;
|
||||||
|
|
||||||
|
public PodLogDownloader(TestLog log, IK8sManager k8sManager)
|
||||||
|
{
|
||||||
|
this.log = log;
|
||||||
|
k8SManager = k8sManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodexNodeLog DownloadLog(OnlineCodexNode node)
|
||||||
|
{
|
||||||
|
var description = node.Describe();
|
||||||
|
var subFile = log.CreateSubfile();
|
||||||
|
|
||||||
|
log.Log($"Downloading logs for {description} to file {subFile.FilenameWithoutPath}");
|
||||||
|
var handler = new PodLogDownloadHandler(description, subFile);
|
||||||
|
k8SManager.FetchPodLog(node, handler);
|
||||||
|
return handler.CreateCodexNodeLog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PodLogDownloadHandler : IPodLogHandler
|
||||||
|
{
|
||||||
|
private readonly string description;
|
||||||
|
private readonly LogFile log;
|
||||||
|
|
||||||
|
public PodLogDownloadHandler(string description, LogFile log)
|
||||||
|
{
|
||||||
|
this.description = description;
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodexNodeLog CreateCodexNodeLog()
|
||||||
|
{
|
||||||
|
return new CodexNodeLog(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(Stream stream)
|
||||||
|
{
|
||||||
|
log.Write($"{description} -->> {log.FilenameWithoutPath}");
|
||||||
|
log.WriteRaw(description);
|
||||||
|
var reader = new StreamReader(stream);
|
||||||
|
var line = reader.ReadLine();
|
||||||
|
while (line != null)
|
||||||
|
{
|
||||||
|
log.WriteRaw(line);
|
||||||
|
line = reader.ReadLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
namespace CodexDistTestCore
|
|
||||||
{
|
|
||||||
public interface IPodLogsHandler
|
|
||||||
{
|
|
||||||
void Log(int id, string podDescription, Stream log);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,20 @@
|
||||||
using NUnit.Framework;
|
using CodexDistTestCore.Config;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace CodexDistTestCore
|
namespace CodexDistTestCore
|
||||||
{
|
{
|
||||||
public class TestLog
|
public class TestLog
|
||||||
{
|
{
|
||||||
public const string LogRoot = "D:/CodexTestLogs";
|
private readonly NumberSource subfileNumberSource = new NumberSource(0);
|
||||||
private readonly LogFile file;
|
private readonly LogFile file;
|
||||||
|
private readonly DateTime now;
|
||||||
|
|
||||||
public TestLog()
|
public TestLog()
|
||||||
{
|
{
|
||||||
|
now = DateTime.UtcNow;
|
||||||
|
|
||||||
var name = GetTestName();
|
var name = GetTestName();
|
||||||
file = new LogFile(name);
|
file = new LogFile(now, name);
|
||||||
|
|
||||||
Log($"Begin: {name}");
|
Log($"Begin: {name}");
|
||||||
}
|
}
|
||||||
|
@ -25,26 +29,23 @@ namespace CodexDistTestCore
|
||||||
Log($"[ERROR] {message}");
|
Log($"[ERROR] {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndTest(K8sManager k8sManager)
|
public void EndTest()
|
||||||
{
|
{
|
||||||
var result = TestContext.CurrentContext.Result;
|
var result = TestContext.CurrentContext.Result;
|
||||||
|
|
||||||
Log($"Finished: {GetTestName()} = {result.Outcome.Status}");
|
Log($"Finished: {GetTestName()} = {result.Outcome.Status}");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(result.Message))
|
if (!string.IsNullOrEmpty(result.Message))
|
||||||
{
|
{
|
||||||
Log(result.Message);
|
Log(result.Message);
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Failed)
|
|
||||||
{
|
|
||||||
Log($"{result.StackTrace}");
|
Log($"{result.StackTrace}");
|
||||||
|
|
||||||
var logWriter = new PodLogWriter(file);
|
|
||||||
logWriter.IncludeFullPodLogging(k8sManager);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LogFile CreateSubfile()
|
||||||
|
{
|
||||||
|
return new LogFile(now, $"{GetTestName()}_{subfileNumberSource.GetNextNumber().ToString().PadLeft(6, '0')}");
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetTestName()
|
private static string GetTestName()
|
||||||
{
|
{
|
||||||
var test = TestContext.CurrentContext.Test;
|
var test = TestContext.CurrentContext.Test;
|
||||||
|
@ -60,70 +61,36 @@ namespace CodexDistTestCore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PodLogWriter : IPodLogsHandler
|
|
||||||
{
|
|
||||||
private readonly LogFile file;
|
|
||||||
|
|
||||||
public PodLogWriter(LogFile file)
|
|
||||||
{
|
|
||||||
this.file = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void IncludeFullPodLogging(K8sManager k8sManager)
|
|
||||||
{
|
|
||||||
file.Write("Full pod logging:");
|
|
||||||
k8sManager.FetchAllPodsLogs(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Log(int id, string podDescription, Stream log)
|
|
||||||
{
|
|
||||||
var logFile = id.ToString().PadLeft(6, '0');
|
|
||||||
file.Write($"{podDescription} -->> {logFile}");
|
|
||||||
LogRaw(podDescription, logFile);
|
|
||||||
var reader = new StreamReader(log);
|
|
||||||
var line = reader.ReadLine();
|
|
||||||
while (line != null)
|
|
||||||
{
|
|
||||||
LogRaw(line, logFile);
|
|
||||||
line = reader.ReadLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LogRaw(string message, string filename)
|
|
||||||
{
|
|
||||||
file!.WriteRaw(message, filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LogFile
|
public class LogFile
|
||||||
{
|
{
|
||||||
private readonly string filepath;
|
private readonly string filepath;
|
||||||
private readonly string filename;
|
|
||||||
|
|
||||||
public LogFile(string name)
|
public LogFile(DateTime now, string name)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
|
|
||||||
filepath = Path.Join(
|
filepath = Path.Join(
|
||||||
TestLog.LogRoot,
|
LogConfig.LogRoot,
|
||||||
$"{now.Year}-{Pad(now.Month)}",
|
$"{now.Year}-{Pad(now.Month)}",
|
||||||
Pad(now.Day));
|
Pad(now.Day));
|
||||||
|
|
||||||
Directory.CreateDirectory(filepath);
|
Directory.CreateDirectory(filepath);
|
||||||
|
|
||||||
filename = Path.Combine(filepath, $"{Pad(now.Hour)}-{Pad(now.Minute)}-{Pad(now.Second)}Z_{name.Replace('.', '-')}");
|
FilenameWithoutPath = $"{Pad(now.Hour)}-{Pad(now.Minute)}-{Pad(now.Second)}Z_{name.Replace('.', '-')}.log";
|
||||||
|
FullFilename = Path.Combine(filepath, FilenameWithoutPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string FullFilename { get; }
|
||||||
|
public string FilenameWithoutPath { get; }
|
||||||
|
|
||||||
public void Write(string message)
|
public void Write(string message)
|
||||||
{
|
{
|
||||||
WriteRaw($"{GetTimestamp()} {message}");
|
WriteRaw($"{GetTimestamp()} {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteRaw(string message, string subfile = "")
|
public void WriteRaw(string message)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.AppendAllLines(filename + subfile + ".log", new[] { message });
|
File.AppendAllLines(FullFilename, new[] { message });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using CodexDistTestCore;
|
using CodexDistTestCore;
|
||||||
|
using CodexDistTestCore.Config;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace Tests.BasicTests
|
namespace Tests.BasicTests
|
||||||
|
@ -19,13 +20,20 @@ namespace Tests.BasicTests
|
||||||
Assert.That(debugInfo.codex.revision, Is.EqualTo(dockerImage.GetExpectedImageRevision()));
|
Assert.That(debugInfo.codex.revision, Is.EqualTo(dockerImage.GetExpectedImageRevision()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test, DontDownloadLogsOnFailure]
|
||||||
|
public void CanAccessLogs()
|
||||||
|
{
|
||||||
|
var node = SetupCodexNodes(1).BringOnline()[0];
|
||||||
|
|
||||||
|
var log = node.DownloadLog();
|
||||||
|
|
||||||
|
log.AssertLogContains("Started codex node");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void OneClientTest()
|
public void OneClientTest()
|
||||||
{
|
{
|
||||||
var primary = SetupCodexNodes(1)
|
var primary = SetupCodexNodes(1).BringOnline()[0];
|
||||||
.WithLogLevel(CodexLogLevel.Trace)
|
|
||||||
.WithStorageQuota(2.MB())
|
|
||||||
.BringOnline()[0];
|
|
||||||
|
|
||||||
var testFile = GenerateTestFile(1.MB());
|
var testFile = GenerateTestFile(1.MB());
|
||||||
|
|
||||||
|
@ -37,12 +45,9 @@ namespace Tests.BasicTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TwoClientOnePodTest()
|
public void TwoClientsOnePodTest()
|
||||||
{
|
{
|
||||||
var group = SetupCodexNodes(2)
|
var group = SetupCodexNodes(2).BringOnline();
|
||||||
.WithLogLevel(CodexLogLevel.Trace)
|
|
||||||
.WithStorageQuota(2.MB())
|
|
||||||
.BringOnline();
|
|
||||||
|
|
||||||
var primary = group[0];
|
var primary = group[0];
|
||||||
var secondary = group[1];
|
var secondary = group[1];
|
||||||
|
@ -51,14 +56,24 @@ namespace Tests.BasicTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TwoClientTwoPodTest()
|
public void TwoClientsTwoPodsTest()
|
||||||
|
{
|
||||||
|
var primary = SetupCodexNodes(1).BringOnline()[0];
|
||||||
|
|
||||||
|
var secondary = SetupCodexNodes(1).BringOnline()[0];
|
||||||
|
|
||||||
|
PerformTwoClientTest(primary, secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TwoClientsTwoLocationsTest()
|
||||||
{
|
{
|
||||||
var primary = SetupCodexNodes(1)
|
var primary = SetupCodexNodes(1)
|
||||||
.WithStorageQuota(2.MB())
|
.At(Location.BensLaptop)
|
||||||
.BringOnline()[0];
|
.BringOnline()[0];
|
||||||
|
|
||||||
var secondary = SetupCodexNodes(1)
|
var secondary = SetupCodexNodes(1)
|
||||||
.WithStorageQuota(2.MB())
|
.At(Location.BensOldGamingMachine)
|
||||||
.BringOnline()[0];
|
.BringOnline()[0];
|
||||||
|
|
||||||
PerformTwoClientTest(primary, secondary);
|
PerformTwoClientTest(primary, secondary);
|
||||||
|
|
Loading…
Reference in New Issue