2
0
mirror of synced 2025-01-12 09:34:40 +00:00

Extracts plugin mapping to core assembly.

This commit is contained in:
benbierens 2023-09-12 14:50:18 +02:00
parent dc1bed6861
commit 8849c4dfa7
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
12 changed files with 231 additions and 168 deletions

View File

@ -0,0 +1,38 @@
using Core;
using KubernetesWorkflow;
namespace CodexPlugin
{
public static class CoreInterfaceExtensions
{
public static RunningContainers[] StartCodexNodes(this CoreInterface ci, int number, Action<ICodexSetup> setup)
{
return Plugin(ci).StartCodexNodes(number, setup);
}
public static ICodexNodeGroup WrapCodexContainers(this CoreInterface ci, RunningContainers[] containers)
{
return Plugin(ci).WrapCodexContainers(containers);
}
public static IOnlineCodexNode SetupCodexNode(this CoreInterface ci, Action<ICodexSetup> setup)
{
return Plugin(ci).SetupCodexNode(setup);
}
public static ICodexNodeGroup SetupCodexNodes(this CoreInterface ci, int number, Action<ICodexSetup> setup)
{
return Plugin(ci).SetupCodexNodes(number, setup);
}
public static ICodexNodeGroup SetupCodexNodes(this CoreInterface ci, int number)
{
return Plugin(ci).SetupCodexNodes(number);
}
private static CodexPlugin Plugin(CoreInterface ci)
{
return ci.GetPlugin<CodexPlugin>();
}
}
}

View File

@ -1,38 +0,0 @@
using Core;
using KubernetesWorkflow;
namespace CodexPlugin
{
public static class PluginInterfaceExtensions
{
public static RunningContainers[] StartCodexNodes(this PluginInterface pluginInterface, int number, Action<ICodexSetup> setup)
{
return Plugin(pluginInterface).StartCodexNodes(number, setup);
}
public static ICodexNodeGroup WrapCodexContainers(this PluginInterface pluginInterface, RunningContainers[] containers)
{
return Plugin(pluginInterface).WrapCodexContainers(containers);
}
public static IOnlineCodexNode SetupCodexNode(this PluginInterface pluginInterface, Action<ICodexSetup> setup)
{
return Plugin(pluginInterface).SetupCodexNode(setup);
}
public static ICodexNodeGroup SetupCodexNodes(this PluginInterface pluginInterface, int number, Action<ICodexSetup> setup)
{
return Plugin(pluginInterface).SetupCodexNodes(number, setup);
}
public static ICodexNodeGroup SetupCodexNodes(this PluginInterface pluginInterface, int number)
{
return Plugin(pluginInterface).SetupCodexNodes(number);
}
private static CodexPlugin Plugin(PluginInterface pluginInterface)
{
return pluginInterface.GetPlugin<CodexPlugin>();
}
}
}

23
Core/CoreInterface.cs Normal file
View File

@ -0,0 +1,23 @@
namespace Core
{
public class CoreInterface
{
private static readonly Dictionary<CoreInterface, EntryPoint> coreAssociations = new Dictionary<CoreInterface, EntryPoint>();
public T GetPlugin<T>() where T : IProjectPlugin
{
return coreAssociations[this].GetPlugin<T>();
}
internal static void Associate(CoreInterface coreInterface, EntryPoint entryPoint)
{
coreAssociations.Add(coreInterface, entryPoint);
}
internal static void Desociate(EntryPoint entryPoint)
{
var key = coreAssociations.Single(p => p.Value == entryPoint).Key;
coreAssociations.Remove(key);
}
}
}

90
Core/EntryPoint.cs Normal file
View File

@ -0,0 +1,90 @@
using FileUtils;
using KubernetesWorkflow;
using Logging;
using Utils;
namespace Core
{
public class EntryPoint : IPluginTools
{
private readonly PluginManager manager = new PluginManager();
private readonly ILog log;
private readonly ITimeSet timeSet;
private readonly FileManager fileManager;
private readonly WorkflowCreator workflowCreator;
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder, ITimeSet timeSet)
{
this.log = log;
this.timeSet = timeSet;
fileManager = new FileManager(log, fileManagerRootFolder);
workflowCreator = new WorkflowCreator(log, configuration);
manager.InstantiatePlugins(PluginFinder.GetPluginTypes());
}
public EntryPoint(ILog log, Configuration configuration, string fileManagerRootFolder)
: this(log, configuration, fileManagerRootFolder, new DefaultTimeSet())
{
}
public void Announce()
{
manager.AnnouncePlugins(log);
}
public void Initialize()
{
manager.InitializePlugins(this);
}
public void ManuallyAssociateCoreInterface(CoreInterface ci)
{
CoreInterface.Associate(ci, this);
}
public CoreInterface CreateInterface()
{
var ci = new CoreInterface();
CoreInterface.Associate(ci, this);
return ci;
}
public void Decommission()
{
CoreInterface.Desociate(this);
manager.FinalizePlugins(log);
}
internal T GetPlugin<T>() where T : IProjectPlugin
{
return manager.GetPlugin<T>();
}
public Http CreateHttp(Address address, string baseUrl, Action<HttpClient> onClientCreated, string? logAlias = null)
{
return new Http(log, timeSet, address, baseUrl, onClientCreated, logAlias);
}
public Http CreateHttp(Address address, string baseUrl, string? logAlias = null)
{
return new Http(log, timeSet, address, baseUrl, logAlias);
}
public IStartupWorkflow CreateWorkflow(string? namespaceOverride = null)
{
if (namespaceOverride != null) throw new Exception("Namespace override is not supported in the DistTest environment. (It would mess up automatic resource cleanup.)");
return workflowCreator.CreateWorkflow();
}
public IFileManager GetFileManager()
{
return fileManager;
}
public ILog GetLog()
{
return log;
}
}
}

46
Core/PluginFinder.cs Normal file
View File

@ -0,0 +1,46 @@
using System.Reflection;
namespace Core
{
public static class PluginFinder
{
private static Type[]? pluginTypes = null;
public static Type[] GetPluginTypes()
{
if (pluginTypes != null) return pluginTypes;
// Reflection can be costly. Do this only once.
FindAndLoadPluginAssemblies();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
pluginTypes = assemblies.SelectMany(a => a.GetTypes().Where(t =>
typeof(IProjectPlugin).IsAssignableFrom(t) &&
!t.IsAbstract)
).ToArray();
return pluginTypes;
}
private static void FindAndLoadPluginAssemblies()
{
var files = Directory.GetFiles(".");
foreach (var file in files)
{
var f = file.ToLowerInvariant();
if (f.Contains("plugin") && f.EndsWith("dll"))
{
var name = Path.GetFileNameWithoutExtension(file);
try
{
Assembly.Load(name);
}
catch (Exception ex)
{
throw new Exception($"Failed to load plugin from file '{name}'.", ex);
}
}
}
}
}
}

View File

@ -1,7 +0,0 @@
namespace Core
{
public abstract class PluginInterface
{
public abstract T GetPlugin<T>() where T : IProjectPlugin;
}
}

View File

@ -10,10 +10,9 @@ namespace Core
{ {
private readonly List<IProjectPlugin> projectPlugins = new List<IProjectPlugin>(); private readonly List<IProjectPlugin> projectPlugins = new List<IProjectPlugin>();
public void DiscoverPlugins() public void InstantiatePlugins(Type[] pluginTypes)
{ {
projectPlugins.Clear(); projectPlugins.Clear();
var pluginTypes = PluginFinder.GetPluginTypes();
foreach (var pluginType in pluginTypes) foreach (var pluginType in pluginTypes)
{ {
var plugin = (IProjectPlugin)Activator.CreateInstance(pluginType)!; var plugin = (IProjectPlugin)Activator.CreateInstance(pluginType)!;
@ -42,48 +41,6 @@ namespace Core
} }
} }
public static class PluginFinder
{
private static Type[]? pluginTypes = null;
public static Type[] GetPluginTypes()
{
if (pluginTypes != null) return pluginTypes;
// Reflection can be costly. Do this only once.
FindAndLoadPluginAssemblies();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
pluginTypes = assemblies.SelectMany(a => a.GetTypes().Where(t =>
typeof(IProjectPlugin).IsAssignableFrom(t) &&
!t.IsAbstract)
).ToArray();
return pluginTypes;
}
private static void FindAndLoadPluginAssemblies()
{
var files = Directory.GetFiles(".");
foreach (var file in files)
{
var f = file.ToLowerInvariant();
if (f.Contains("plugin") && f.EndsWith("dll"))
{
var name = Path.GetFileNameWithoutExtension(file);
try
{
Assembly.Load(name);
}
catch (Exception ex)
{
throw new Exception($"Failed to load plugin from file '{name}'.", ex);
}
}
}
}
}
public interface IProjectPlugin public interface IProjectPlugin
{ {
void Announce(ILog log); void Announce(ILog log);

View File

@ -31,12 +31,13 @@ namespace DistTestCore
this.k8sNamespacePrefix = k8sNamespacePrefix; this.k8sNamespacePrefix = k8sNamespacePrefix;
} }
public KubernetesWorkflow.Configuration GetK8sConfiguration(ITimeSet timeSet) public KubernetesWorkflow.Configuration GetK8sConfiguration(ITimeSet timeSet, string k8sNamespace)
{ {
return new KubernetesWorkflow.Configuration( return new KubernetesWorkflow.Configuration(
kubeConfigFile: kubeConfigFile, kubeConfigFile: kubeConfigFile,
operationTimeout: timeSet.K8sOperationTimeout(), operationTimeout: timeSet.K8sOperationTimeout(),
retryDelay: timeSet.WaitForK8sServiceDelay() retryDelay: timeSet.WaitForK8sServiceDelay(),
kubernetesNamespace: k8sNamespace
); );
} }

View File

@ -1,6 +1,5 @@
using Core; using Core;
using FileUtils; using FileUtils;
using KubernetesWorkflow;
using Logging; using Logging;
using NUnit.Framework; using NUnit.Framework;
using System.Reflection; using System.Reflection;
@ -9,7 +8,7 @@ using Utils;
namespace DistTestCore namespace DistTestCore
{ {
[Parallelizable(ParallelScope.All)] [Parallelizable(ParallelScope.All)]
public abstract class DistTest : PluginInterface public abstract class DistTest : CoreInterface
{ {
private const string TestsType = "dist-tests"; private const string TestsType = "dist-tests";
private const string TestNamespacePrefix = "ct-"; private const string TestNamespacePrefix = "ct-";
@ -18,8 +17,8 @@ namespace DistTestCore
private readonly FixtureLog fixtureLog; private readonly FixtureLog fixtureLog;
private readonly StatusLog statusLog; private readonly StatusLog statusLog;
private readonly object lifecycleLock = new object(); private readonly object lifecycleLock = new object();
private readonly EntryPoint globalEntryPoint;
private readonly Dictionary<string, TestLifecycle> lifecycles = new Dictionary<string, TestLifecycle>(); private readonly Dictionary<string, TestLifecycle> lifecycles = new Dictionary<string, TestLifecycle>();
private readonly PluginManager PluginManager = new PluginManager();
public DistTest() public DistTest()
{ {
@ -30,14 +29,15 @@ namespace DistTestCore
var startTime = DateTime.UtcNow; var startTime = DateTime.UtcNow;
fixtureLog = new FixtureLog(logConfig, startTime); fixtureLog = new FixtureLog(logConfig, startTime);
statusLog = new StatusLog(logConfig, startTime); statusLog = new StatusLog(logConfig, startTime);
globalEntryPoint = new EntryPoint(fixtureLog, configuration.GetK8sConfiguration(new DefaultTimeSet(), TestNamespacePrefix), configuration.GetFileManagerFolder());
} }
[OneTimeSetUp] [OneTimeSetUp]
public void GlobalSetup() public void GlobalSetup()
{ {
fixtureLog.Log($"Distributed Tests are starting..."); fixtureLog.Log($"Distributed Tests are starting...");
PluginManager.DiscoverPlugins(); globalEntryPoint.Announce();
AnnouncePlugins(fixtureLog);
// Previous test run may have been interrupted. // Previous test run may have been interrupted.
// Begin by cleaning everything up. // Begin by cleaning everything up.
@ -45,8 +45,7 @@ namespace DistTestCore
{ {
Stopwatch.Measure(fixtureLog, "Global setup", () => Stopwatch.Measure(fixtureLog, "Global setup", () =>
{ {
var wc = new WorkflowCreator(fixtureLog, configuration.GetK8sConfiguration(GetTimeSet()), string.Empty); globalEntryPoint.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix);
wc.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix);
}); });
} }
catch (Exception ex) catch (Exception ex)
@ -62,7 +61,7 @@ namespace DistTestCore
[OneTimeTearDown] [OneTimeTearDown]
public void GlobalTearDown() public void GlobalTearDown()
{ {
FinalizePlugins(fixtureLog); globalEntryPoint.Decommission();
} }
[SetUp] [SetUp]
@ -94,7 +93,7 @@ namespace DistTestCore
public TrackedFile GenerateTestFile(ByteSize size, string label = "") public TrackedFile GenerateTestFile(ByteSize size, string label = "")
{ {
return Get().FileManager.GenerateTestFile(size, label); return Get().EntryPoint.GetFileManager().GenerateTestFile(size, label);
} }
/// <summary> /// <summary>
@ -103,7 +102,7 @@ namespace DistTestCore
/// </summary> /// </summary>
public void ScopedTestFiles(Action action) public void ScopedTestFiles(Action action)
{ {
Get().FileManager.ScopedFiles(action); Get().EntryPoint.GetFileManager().ScopedFiles(action);
} }
//public IOnlineCodexNode SetupCodexBootstrapNode() //public IOnlineCodexNode SetupCodexBootstrapNode()
@ -154,20 +153,10 @@ namespace DistTestCore
// return Get().CodexStarter.RunningGroups.SelectMany(g => g.Nodes); // return Get().CodexStarter.RunningGroups.SelectMany(g => g.Nodes);
//} //}
public override T GetPlugin<T>() //public override T GetPlugin<T>()
{ //{
return Get().GetPlugin<T>(); // return Get().GetPlugin<T>();
} //}
private void AnnouncePlugins(FixtureLog fixtureLog)
{
PluginManager.AnnouncePlugins(fixtureLog);
}
private void FinalizePlugins(FixtureLog fixtureLog)
{
PluginManager.FinalizePlugins(fixtureLog);
}
public ILog GetTestLog() public ILog GetTestLog()
{ {
@ -224,6 +213,7 @@ namespace DistTestCore
{ {
var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString(); var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString();
var lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(), configuration, GetTimeSet(), testNamespace); var lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(), configuration, GetTimeSet(), testNamespace);
lifecycle.EntryPoint.ManuallyAssociateCoreInterface(this);
lifecycles.Add(testName, lifecycle); lifecycles.Add(testName, lifecycle);
DefaultContainerRecipe.TestsType = TestsType; DefaultContainerRecipe.TestsType = TestsType;
//DefaultContainerRecipe.ApplicationIds = lifecycle.GetApplicationIds(); //DefaultContainerRecipe.ApplicationIds = lifecycle.GetApplicationIds();
@ -243,6 +233,7 @@ namespace DistTestCore
WriteEndTestLog(lifecycle.Log); WriteEndTestLog(lifecycle.Log);
IncludeLogsAndMetricsOnTestFailure(lifecycle); IncludeLogsAndMetricsOnTestFailure(lifecycle);
lifecycle.EntryPoint.Decommission();
lifecycle.DeleteAllResources(); lifecycle.DeleteAllResources();
lifecycle = null!; lifecycle = null!;
}); });

View File

@ -1,31 +1,22 @@
using Core; using Core;
using FileUtils;
using KubernetesWorkflow;
using Logging; using Logging;
using Utils; using Utils;
namespace DistTestCore namespace DistTestCore
{ {
public class TestLifecycle : IPluginTools public class TestLifecycle
{ {
private readonly PluginManager pluginManager;
private readonly DateTime testStart; private readonly DateTime testStart;
private readonly WorkflowCreator workflowCreator;
public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet, string testNamespace) public TestLifecycle(TestLog log, Configuration configuration, ITimeSet timeSet, string testNamespace)
{ {
Log = log; Log = log;
Configuration = configuration; Configuration = configuration;
TimeSet = timeSet; TimeSet = timeSet;
TestNamespace = testNamespace;
testStart = DateTime.UtcNow; testStart = DateTime.UtcNow;
FileManager = new FileManager(Log, Configuration.GetFileManagerFolder());
workflowCreator = new WorkflowCreator(Log, Configuration.GetK8sConfiguration(TimeSet), TestNamespace); EntryPoint = new EntryPoint(log, configuration.GetK8sConfiguration(timeSet, testNamespace), configuration.GetFileManagerFolder(), timeSet);
EntryPoint.Initialize();
pluginManager = new PluginManager();
pluginManager.DiscoverPlugins();
pluginManager.InitializePlugins(this);
log.WriteLogTag(); log.WriteLogTag();
} }
@ -33,44 +24,12 @@ namespace DistTestCore
public TestLog Log { get; } public TestLog Log { get; }
public Configuration Configuration { get; } public Configuration Configuration { get; }
public ITimeSet TimeSet { get; } public ITimeSet TimeSet { get; }
public string TestNamespace { get; } public EntryPoint EntryPoint { get; }
public IFileManager FileManager { get; }
public T GetPlugin<T>() where T : IProjectPlugin
{
return pluginManager.GetPlugin<T>();
}
public Http CreateHttp(Address address, string baseUrl, Action<HttpClient> onClientCreated, string? logAlias = null)
{
return new Http(Log, TimeSet, address, baseUrl, onClientCreated, logAlias);
}
public Http CreateHttp(Address address, string baseUrl, string? logAlias = null)
{
return new Http(Log, TimeSet, address, baseUrl, logAlias);
}
public IStartupWorkflow CreateWorkflow(string? namespaceOverride = null)
{
if (namespaceOverride != null) throw new Exception("Namespace override is not supported in the DistTest environment. (It would mess up automatic resource cleanup.)");
return workflowCreator.CreateWorkflow();
}
public IFileManager GetFileManager()
{
return FileManager;
}
public ILog GetLog()
{
return Log;
}
public void DeleteAllResources() public void DeleteAllResources()
{ {
CreateWorkflow().DeleteNamespace(); EntryPoint.CreateWorkflow().DeleteNamespace();
FileManager.DeleteAllTestFiles(); EntryPoint.GetFileManager().DeleteAllTestFiles();
} }
//public IDownloadedLog DownloadLog(RunningContainer container, int? tailLines = null) //public IDownloadedLog DownloadLog(RunningContainer container, int? tailLines = null)

View File

@ -2,15 +2,17 @@
{ {
public class Configuration public class Configuration
{ {
public Configuration(string? kubeConfigFile, TimeSpan operationTimeout, TimeSpan retryDelay) public Configuration(string? kubeConfigFile, TimeSpan operationTimeout, TimeSpan retryDelay, string kubernetesNamespace)
{ {
KubeConfigFile = kubeConfigFile; KubeConfigFile = kubeConfigFile;
OperationTimeout = operationTimeout; OperationTimeout = operationTimeout;
RetryDelay = retryDelay; RetryDelay = retryDelay;
KubernetesNamespace = kubernetesNamespace;
} }
public string? KubeConfigFile { get; } public string? KubeConfigFile { get; }
public TimeSpan OperationTimeout { get; } public TimeSpan OperationTimeout { get; }
public TimeSpan RetryDelay { get; } public TimeSpan RetryDelay { get; }
public string KubernetesNamespace { get; }
} }
} }

View File

@ -10,13 +10,14 @@ namespace KubernetesWorkflow
private readonly KnownK8sPods knownPods = new KnownK8sPods(); private readonly KnownK8sPods knownPods = new KnownK8sPods();
private readonly K8sCluster cluster; private readonly K8sCluster cluster;
private readonly ILog log; private readonly ILog log;
private readonly string testNamespace; private readonly string k8sNamespace;
public WorkflowCreator(ILog log, Configuration configuration, string testNamespace) public WorkflowCreator(ILog log, Configuration configuration)
{ {
cluster = new K8sCluster(configuration);
this.log = log; this.log = log;
this.testNamespace = testNamespace.ToLowerInvariant();
cluster = new K8sCluster(configuration);
k8sNamespace = configuration.KubernetesNamespace.ToLowerInvariant();
} }
public IStartupWorkflow CreateWorkflow() public IStartupWorkflow CreateWorkflow()
@ -24,7 +25,7 @@ namespace KubernetesWorkflow
var workflowNumberSource = new WorkflowNumberSource(numberSource.GetNextNumber(), var workflowNumberSource = new WorkflowNumberSource(numberSource.GetNextNumber(),
containerNumberSource); containerNumberSource);
return new StartupWorkflow(log, workflowNumberSource, cluster, knownPods, testNamespace); return new StartupWorkflow(log, workflowNumberSource, cluster, knownPods, k8sNamespace);
} }
} }
} }