diff --git a/DistTestCore/CodexLogs/CodexNodeLog.cs b/DistTestCore/CodexLogs/CodexNodeLog.cs new file mode 100644 index 00000000..5003bf8b --- /dev/null +++ b/DistTestCore/CodexLogs/CodexNodeLog.cs @@ -0,0 +1,35 @@ +using Logging; +using NUnit.Framework; + +namespace DistTestCore.CodexLogs +{ + public interface ICodexNodeLog + { + void AssertLogContains(string expectedString); + } + + public class CodexNodeLog : ICodexNodeLog + { + 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}"); + } + } +} diff --git a/DistTestCore/CodexLogs/LogDownloadHandler.cs b/DistTestCore/CodexLogs/LogDownloadHandler.cs new file mode 100644 index 00000000..3848b288 --- /dev/null +++ b/DistTestCore/CodexLogs/LogDownloadHandler.cs @@ -0,0 +1,35 @@ +using KubernetesWorkflow; +using Logging; + +namespace DistTestCore.CodexLogs +{ + public class LogDownloadHandler : ILogHandler + { + private readonly string description; + private readonly LogFile log; + + public LogDownloadHandler(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(); + } + } + } +} diff --git a/DistTestCore/CodexStarter.cs b/DistTestCore/CodexStarter.cs index 8ad0aa8b..41c685fa 100644 --- a/DistTestCore/CodexStarter.cs +++ b/DistTestCore/CodexStarter.cs @@ -1,5 +1,7 @@ using DistTestCore.Codex; +using DistTestCore.CodexLogs; using KubernetesWorkflow; +using Nethereum.Merkle.Patricia; namespace DistTestCore { @@ -37,6 +39,12 @@ namespace DistTestCore workflow.DeleteAllResources(); } + public void DownloadLog(RunningContainer container, ILogHandler logHandler) + { + var workflow = CreateWorkflow(); + workflow.DownloadContainerLog(container, logHandler); + } + private StartupWorkflow CreateWorkflow() { return workflowCreator.CreateWorkflow(); diff --git a/DistTestCore/OnlineCodexNode.cs b/DistTestCore/OnlineCodexNode.cs index a4082a85..9ecaef5e 100644 --- a/DistTestCore/OnlineCodexNode.cs +++ b/DistTestCore/OnlineCodexNode.cs @@ -1,4 +1,5 @@ using DistTestCore.Codex; +using DistTestCore.CodexLogs; using NUnit.Framework; namespace DistTestCore @@ -9,7 +10,7 @@ namespace DistTestCore ContentId UploadFile(TestFile file); TestFile? DownloadContent(ContentId contentId); void ConnectToPeer(IOnlineCodexNode node); - //ICodexNodeLog DownloadLog(); + ICodexNodeLog DownloadLog(); //IMetricsAccess Metrics { get; } //IMarketplaceAccess Marketplace { get; } } @@ -76,10 +77,10 @@ namespace DistTestCore Log($"Successfully connected to peer {peer.GetName()}."); } - //public ICodexNodeLog DownloadLog() - //{ - // return Group.DownloadLog(this); - //} + public ICodexNodeLog DownloadLog() + { + return lifecycle.DownloadLog(this); + } public string Describe() { diff --git a/DistTestCore/TestLifecycle.cs b/DistTestCore/TestLifecycle.cs index 7f350568..95f4508e 100644 --- a/DistTestCore/TestLifecycle.cs +++ b/DistTestCore/TestLifecycle.cs @@ -1,4 +1,5 @@ -using Logging; +using DistTestCore.CodexLogs; +using Logging; namespace DistTestCore { @@ -20,5 +21,16 @@ namespace DistTestCore CodexStarter.DeleteAllResources(); FileManager.DeleteAllTestFiles(); } + + public ICodexNodeLog DownloadLog(OnlineCodexNode node) + { + var subFile = Log.CreateSubfile(); + var description = node.Describe(); + var handler = new LogDownloadHandler(description, subFile); + + CodexStarter.DownloadLog(node.CodexAccess.Container, handler); + + return new CodexNodeLog(subFile); + } } } diff --git a/KubernetesWorkflow/K8sController.cs b/KubernetesWorkflow/K8sController.cs index a3b25faf..eb492a93 100644 --- a/KubernetesWorkflow/K8sController.cs +++ b/KubernetesWorkflow/K8sController.cs @@ -43,6 +43,12 @@ namespace KubernetesWorkflow WaitUntilPodOffline(pod.Name); } + public void DownloadPodLog(RunningPod pod, ContainerRecipe recipe, ILogHandler logHandler) + { + var stream = client.ReadNamespacedPodLog(pod.Name, K8sNamespace, recipe.Name); + logHandler.Log(stream); + } + public void DeleteAllResources() { DeleteNamespace(); diff --git a/KubernetesWorkflow/StartupWorkflow.cs b/KubernetesWorkflow/StartupWorkflow.cs index c3faa50c..9dff3281 100644 --- a/KubernetesWorkflow/StartupWorkflow.cs +++ b/KubernetesWorkflow/StartupWorkflow.cs @@ -34,6 +34,14 @@ }); } + public void DownloadContainerLog(RunningContainer container, ILogHandler logHandler) + { + K8s(controller => + { + controller.DownloadPodLog(container.Pod, container.Recipe, logHandler); + }); + } + public void DeleteAllResources() { K8s(controller => @@ -72,6 +80,10 @@ controller.Dispose(); return result; } + } + public interface ILogHandler + { + void Log(Stream log); } } diff --git a/Tests/BasicTests/SimpleTests.cs b/Tests/BasicTests/SimpleTests.cs index a723471f..6f6ce14b 100644 --- a/Tests/BasicTests/SimpleTests.cs +++ b/Tests/BasicTests/SimpleTests.cs @@ -1,4 +1,5 @@ using DistTestCore; +using DistTestCore.Codex; using KubernetesWorkflow; using NUnit.Framework; @@ -63,6 +64,20 @@ namespace Tests.BasicTests PerformTwoClientTest(primary, secondary); } + [Test] + public void CodexLogExample() + { + var primary = SetupCodexNodes(1) + .WithLogLevel(CodexLogLevel.Trace) + .BringOnline()[0]; + + primary.UploadFile(GenerateTestFile(5.MB())); + + var log = primary.DownloadLog(); + + log.AssertLogContains("Uploaded file"); + } + //[Test] //public void TwoMetricsExample() //{