Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions src/ServiceControl.Persistence/PersistenceManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ public class PersistenceManifest

public required string Description { get; set; }

public required string AssemblyName { get; set; }
// Absent on manifests for persisters that no longer ship an assembly, such as RavenDB 3.5
public string? AssemblyName { get; set; }

public required string TypeName { get; set; }
public string? TypeName { get; set; }

public bool IsSupported { get; set; } = true;

Expand Down Expand Up @@ -59,10 +60,7 @@ static PersistenceManifestLibrary()

try
{
foreach (var manifestFile in Directory.EnumerateFiles(assemblyDirectory, "persistence.manifest", SearchOption.AllDirectories))
{
PersistenceManifests.Add(DeserializeManifest(manifestFile));
}
PersistenceManifests.AddRange(LoadManifests(Directory.EnumerateFiles(assemblyDirectory, "persistence.manifest", SearchOption.AllDirectories)));
}
catch (Exception ex)
{
Expand All @@ -71,10 +69,7 @@ static PersistenceManifestLibrary()

try
{
foreach (var manifestFile in DevelopmentPersistenceLocations.ManifestFiles)
{
PersistenceManifests.Add(DeserializeManifest(manifestFile));
}
PersistenceManifests.AddRange(LoadManifests(DevelopmentPersistenceLocations.ManifestFiles));
}
catch (Exception ex)
{
Expand All @@ -84,6 +79,26 @@ static PersistenceManifestLibrary()
PersistenceManifests.ForEach(m => logger.LogInformation("Found persistence manifest for {ManifestDisplayName}", m.DisplayName));
}

// One unreadable manifest must not hide the persisters enumerated after it
internal static List<PersistenceManifest> LoadManifests(IEnumerable<string> manifestFiles)
{
var manifests = new List<PersistenceManifest>();

foreach (var manifestFile in manifestFiles)
{
try
{
manifests.Add(DeserializeManifest(manifestFile));
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to load persistence manifest {ManifestFile}", manifestFile);
}
}

return manifests;
}

static PersistenceManifest DeserializeManifest(string manifestFile)
{
var manifest = JsonSerializer.Deserialize<PersistenceManifest>(File.ReadAllText(manifestFile))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@
<PackageReference Include="NServiceBus.CustomChecks" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="ServiceControl.UnitTests" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
namespace ServiceControl.UnitTests.Infrastructure;

using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using ServiceControl.Persistence;

[TestFixture]
public class PersistenceManifestLoadingTests
{
const string RavenDB = """
{
"Name": "RavenDB",
"DisplayName": "RavenDB",
"Description": "RavenDB ServiceControl persister",
"AssemblyName": "ServiceControl.Persistence.RavenDB",
"TypeName": "ServiceControl.Persistence.RavenDB.RavenPersistenceConfiguration, ServiceControl.Persistence.RavenDB"
}
""";

// Ships so that ServiceControl Management can describe pre-v5 instances, and has no assembly left to name
const string RavenDB35 = """
{
"Name": "RavenDB35",
"IsSupported": false,
"DisplayName": "RavenDB 3.5 (Legacy)",
"Description": "RavenDB 3.5 (Legacy) ServiceControl persister"
}
""";

const string SqlServer = """
{
"Name": "SQLServer",
"DisplayName": "SQL Server",
"Description": "SQL Server ServiceControl persister",
"AssemblyName": "ServiceControl.Persistence.EFCore.SqlServer",
"TypeName": "ServiceControl.Persistence.EFCore.SqlServer.SqlServerPersistenceConfiguration, ServiceControl.Persistence.EFCore.SqlServer"
}
""";

[Test]
public void Legacy_manifest_without_an_assembly_does_not_hide_the_persisters_after_it()
{
var manifests = LoadFrom(new()
{
["RavenDB"] = RavenDB,
["RavenDB35"] = RavenDB35,
["SQLServer"] = SqlServer
});

Assert.That(manifests.Select(m => m.Name), Is.EquivalentTo(["RavenDB", "RavenDB35", "SQLServer"]));
}

[Test]
public void Unreadable_manifest_does_not_hide_the_persisters_after_it()
{
var manifests = LoadFrom(new()
{
["Corrupt"] = "{ this is not json",
["SQLServer"] = SqlServer
});

Assert.That(manifests.Select(m => m.Name), Is.EqualTo(["SQLServer"]));
}

static List<PersistenceManifest> LoadFrom(Dictionary<string, string> persisters)
{
var installDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, TestContext.CurrentContext.Test.ID);

foreach (var (persister, manifest) in persisters)
{
var persisterDirectory = Path.Combine(installDirectory, "Persisters", persister);
Directory.CreateDirectory(persisterDirectory);
File.WriteAllText(Path.Combine(persisterDirectory, "persistence.manifest"), manifest);
}

try
{
return PersistenceManifestLibrary.LoadManifests(Directory.EnumerateFiles(installDirectory, "persistence.manifest", SearchOption.AllDirectories));
}
finally
{
Directory.Delete(installDirectory, true);
}
}
}