Replace BLite with Surreal embedded persistence
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m21s
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m21s
This commit is contained in:
@@ -3,10 +3,13 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using System.Text.Json;
|
||||
using ZB.MOM.WW.CBDDC.Core.Network;
|
||||
using ZB.MOM.WW.CBDDC.Core.Storage;
|
||||
using ZB.MOM.WW.CBDDC.Core.Sync;
|
||||
using ZB.MOM.WW.CBDDC.Network;
|
||||
using ZB.MOM.WW.CBDDC.Persistence.BLite;
|
||||
using ZB.MOM.WW.CBDDC.Persistence.Snapshot;
|
||||
using ZB.MOM.WW.CBDDC.Persistence.Surreal;
|
||||
|
||||
namespace ZB.MOM.WW.CBDDC.Sample.Console;
|
||||
|
||||
@@ -16,6 +19,8 @@ internal class Program
|
||||
{
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
if (await TryRunMigrationAsync(args)) return;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Configuration
|
||||
@@ -55,11 +60,20 @@ internal class Program
|
||||
// Database path
|
||||
string dataPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "data");
|
||||
Directory.CreateDirectory(dataPath);
|
||||
string databasePath = Path.Combine(dataPath, $"{nodeId}.blite");
|
||||
string databasePath = Path.Combine(dataPath, $"{nodeId}.rocksdb");
|
||||
string surrealDatabase = nodeId.Replace("-", "_", StringComparison.Ordinal);
|
||||
|
||||
// Register CBDDC Services using Fluent Extensions with BLite, SampleDbContext, and SampleDocumentStore
|
||||
// Register CBDDC services with embedded Surreal (RocksDB).
|
||||
builder.Services.AddSingleton<ICBDDCSurrealSchemaInitializer, SampleSurrealSchemaInitializer>();
|
||||
builder.Services.AddSingleton<SampleDbContext>();
|
||||
builder.Services.AddCBDDCCore()
|
||||
.AddCBDDCBLite<SampleDbContext, SampleDocumentStore>(sp => new SampleDbContext(databasePath))
|
||||
.AddCBDDCSurrealEmbedded<SampleDocumentStore>(_ => new CBDDCSurrealEmbeddedOptions
|
||||
{
|
||||
Endpoint = "rocksdb://local",
|
||||
DatabasePath = databasePath,
|
||||
Namespace = "cbddc_sample",
|
||||
Database = surrealDatabase
|
||||
})
|
||||
.AddCBDDCNetwork<StaticPeerNodeConfigurationProvider>(); // useHostedService = true by default
|
||||
|
||||
builder.Services.AddHostedService<ConsoleInteractiveService>(); // Runs the Input Loop
|
||||
@@ -73,6 +87,107 @@ internal class Program
|
||||
await host.RunAsync();
|
||||
}
|
||||
|
||||
private static async Task<bool> TryRunMigrationAsync(string[] args)
|
||||
{
|
||||
int migrateIndex = Array.IndexOf(args, "--migrate-snapshot");
|
||||
if (migrateIndex < 0) return false;
|
||||
|
||||
string snapshotPath = GetRequiredArgumentValue(args, migrateIndex, "--migrate-snapshot");
|
||||
if (!File.Exists(snapshotPath))
|
||||
throw new FileNotFoundException("Snapshot file not found.", snapshotPath);
|
||||
|
||||
string targetPath = GetOptionalArgumentValue(args, "--target-db")
|
||||
?? Path.Combine(Directory.GetCurrentDirectory(), "data", "migration.rocksdb");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(targetPath))!);
|
||||
|
||||
string nodeId = "migration-node";
|
||||
var configProvider = new StaticPeerNodeConfigurationProvider(new PeerNodeConfiguration
|
||||
{
|
||||
NodeId = nodeId,
|
||||
TcpPort = 0,
|
||||
AuthToken = "migration"
|
||||
});
|
||||
|
||||
string databaseName = $"migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
services.AddSingleton<IPeerNodeConfigurationProvider>(configProvider);
|
||||
services.AddSingleton<ICBDDCSurrealSchemaInitializer, SampleSurrealSchemaInitializer>();
|
||||
services.AddSingleton<SampleDbContext>();
|
||||
services.AddCBDDCCore()
|
||||
.AddCBDDCSurrealEmbedded<SampleDocumentStore>(_ => new CBDDCSurrealEmbeddedOptions
|
||||
{
|
||||
Endpoint = "rocksdb://local",
|
||||
DatabasePath = targetPath,
|
||||
Namespace = "cbddc_migration",
|
||||
Database = databaseName
|
||||
});
|
||||
|
||||
using var provider = services.BuildServiceProvider();
|
||||
var snapshotService = provider.GetRequiredService<ISnapshotService>();
|
||||
await using (var snapshotStream = File.OpenRead(snapshotPath))
|
||||
{
|
||||
await snapshotService.ReplaceDatabaseAsync(snapshotStream);
|
||||
}
|
||||
|
||||
await VerifyMigrationAsync(provider, snapshotPath);
|
||||
System.Console.WriteLine($"Migration completed successfully to: {targetPath}");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task VerifyMigrationAsync(IServiceProvider provider, string snapshotPath)
|
||||
{
|
||||
await using var snapshotStream = File.OpenRead(snapshotPath);
|
||||
var source = await JsonSerializer.DeserializeAsync<SnapshotDto>(snapshotStream)
|
||||
?? throw new InvalidOperationException("Unable to deserialize source snapshot.");
|
||||
|
||||
var documentStore = provider.GetRequiredService<IDocumentStore>();
|
||||
var oplogStore = provider.GetRequiredService<IOplogStore>();
|
||||
var peerStore = provider.GetRequiredService<IPeerConfigurationStore>();
|
||||
var confirmationStore = provider.GetService<IPeerOplogConfirmationStore>();
|
||||
|
||||
int destinationDocuments = (await documentStore.ExportAsync()).Count();
|
||||
int destinationOplog = (await oplogStore.ExportAsync()).Count();
|
||||
int destinationPeers = (await peerStore.ExportAsync()).Count();
|
||||
int destinationConfirmations = confirmationStore == null
|
||||
? 0
|
||||
: (await confirmationStore.ExportAsync()).Count();
|
||||
|
||||
if (destinationDocuments != source.Documents.Count ||
|
||||
destinationOplog != source.Oplog.Count ||
|
||||
destinationPeers != source.RemotePeers.Count ||
|
||||
destinationConfirmations != source.PeerConfirmations.Count)
|
||||
throw new InvalidOperationException("Snapshot parity verification failed after migration.");
|
||||
|
||||
if (source.Oplog.Count > 0)
|
||||
{
|
||||
string firstHash = source.Oplog[0].Hash;
|
||||
string lastHash = source.Oplog[^1].Hash;
|
||||
|
||||
var firstEntry = await oplogStore.GetEntryByHashAsync(firstHash);
|
||||
var lastEntry = await oplogStore.GetEntryByHashAsync(lastHash);
|
||||
if (firstEntry == null || lastEntry == null)
|
||||
throw new InvalidOperationException("Oplog hash spot-check failed after migration.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetRequiredArgumentValue(string[] args, int optionIndex, string optionName)
|
||||
{
|
||||
if (optionIndex < 0 || optionIndex + 1 >= args.Length || args[optionIndex + 1].StartsWith("--"))
|
||||
throw new ArgumentException($"Missing value for {optionName}.");
|
||||
|
||||
return args[optionIndex + 1];
|
||||
}
|
||||
|
||||
private static string? GetOptionalArgumentValue(string[] args, string optionName)
|
||||
{
|
||||
int index = Array.IndexOf(args, optionName);
|
||||
if (index < 0) return null;
|
||||
if (index + 1 >= args.Length || args[index + 1].StartsWith("--"))
|
||||
throw new ArgumentException($"Missing value for {optionName}.");
|
||||
return args[index + 1];
|
||||
}
|
||||
|
||||
private class StaticPeerNodeConfigurationProvider : IPeerNodeConfigurationProvider
|
||||
{
|
||||
/// <summary>
|
||||
@@ -112,4 +227,4 @@ internal class Program
|
||||
ConfigurationChanged?.Invoke(this, newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user