Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
- Implemented MigrationCategoryTests to validate migration categorization for startup, release, seed, and data migrations. - Added tests for edge cases, including null, empty, and whitespace migration names. - Created StartupMigrationHostTests to verify the behavior of the migration host with real PostgreSQL instances using Testcontainers. - Included tests for migration execution, schema creation, and handling of pending release migrations. - Added SQL migration files for testing: creating a test table, adding a column, a release migration, and seeding data.
73 lines
3.0 KiB
C#
73 lines
3.0 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using MongoDB.Driver;
|
|
using StellaOps.Concelier.Models;
|
|
using StellaOps.Concelier.Storage.Mongo.Advisories;
|
|
using StellaOps.Concelier.Storage.Postgres.Advisories;
|
|
|
|
namespace StellaOps.Concelier.WebService.DualWrite;
|
|
|
|
/// <summary>
|
|
/// Dual-write advisory store that writes to both MongoDB and PostgreSQL simultaneously.
|
|
/// Used during migration to verify parity between backends.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// MongoDB is the primary store; PostgreSQL writes are best-effort with error logging.
|
|
/// Read operations are always served from MongoDB.
|
|
/// </remarks>
|
|
public sealed class DualWriteAdvisoryStore : IAdvisoryStore
|
|
{
|
|
private readonly AdvisoryStore _mongoStore;
|
|
private readonly IPostgresAdvisoryStore _postgresStore;
|
|
private readonly ILogger<DualWriteAdvisoryStore> _logger;
|
|
|
|
public DualWriteAdvisoryStore(
|
|
AdvisoryStore mongoStore,
|
|
IPostgresAdvisoryStore postgresStore,
|
|
ILogger<DualWriteAdvisoryStore> logger)
|
|
{
|
|
_mongoStore = mongoStore ?? throw new ArgumentNullException(nameof(mongoStore));
|
|
_postgresStore = postgresStore ?? throw new ArgumentNullException(nameof(postgresStore));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task UpsertAsync(Advisory advisory, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
{
|
|
// Write to MongoDB (primary)
|
|
await _mongoStore.UpsertAsync(advisory, cancellationToken, session).ConfigureAwait(false);
|
|
|
|
// Write to PostgreSQL (secondary, best-effort)
|
|
try
|
|
{
|
|
await _postgresStore.UpsertAsync(advisory, sourceId: null, cancellationToken).ConfigureAwait(false);
|
|
_logger.LogDebug("Dual-write success for advisory {AdvisoryKey}", advisory.AdvisoryKey);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log but don't fail - MongoDB is primary during migration
|
|
_logger.LogWarning(ex, "Dual-write to PostgreSQL failed for advisory {AdvisoryKey}. MongoDB write succeeded.", advisory.AdvisoryKey);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<Advisory?> FindAsync(string advisoryKey, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
{
|
|
// Always read from MongoDB during dual-write mode
|
|
return _mongoStore.FindAsync(advisoryKey, cancellationToken, session);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<IReadOnlyList<Advisory>> GetRecentAsync(int limit, CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
{
|
|
// Always read from MongoDB during dual-write mode
|
|
return _mongoStore.GetRecentAsync(limit, cancellationToken, session);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IAsyncEnumerable<Advisory> StreamAsync(CancellationToken cancellationToken, IClientSessionHandle? session = null)
|
|
{
|
|
// Always read from MongoDB during dual-write mode
|
|
return _mongoStore.StreamAsync(cancellationToken, session);
|
|
}
|
|
}
|