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
14 changes: 14 additions & 0 deletions Darling/Darling.Tests/ManagedConfFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,20 @@ public void RenderBody_ContainsCheckpointTimeout()
Assert.Contains("checkpoint_timeout = '15min'", body, StringComparison.Ordinal);
}

/// <summary>Pin: <see cref="ManagedConfFile.RenderBody"/> always renders <c>listen_addresses</c> as
/// exactly loopback (<c>DarlingManagedPostgres.BuildConfAppend</c>'s v1 line) — never the network
/// address an exposed store's command line adds. Every verification path that reads this rendered text
/// (<see cref="ManagedConfMigrationRunner.VerifyStepB"/>, <see cref="ManagedConfMigrationRunner.FindUnstampedManagedFileErrors"/>)
/// depends on that being true — it is the reason the command line always outranks this file for the key,
/// on every exposed store, every start.</summary>
[Fact]
public void RenderBody_ListenAddresses_IsAlwaysLoopbackOnly()
{
var body = ManagedConfFile.RenderBody(SampleInputs());

Assert.Contains("listen_addresses = '127.0.0.1'", body, StringComparison.Ordinal);
}

/// <summary>
/// #4246 (claude-desktop's parity pin, 14:47Z): for EVERY marker in
/// <see cref="DarlingManagedPostgres.AllManagedConfMarkers"/>, every key that marker's OWN legacy builder
Expand Down
61 changes: 61 additions & 0 deletions Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,67 @@ public void VerifyStepB_NewErrorFromManagedFile_Fails()
Assert.Contains("work_mem", outcome.MismatchedKeys);
}

/// <summary>Pin: <see cref="ManagedConfMigrationRunner.FindUnstampedManagedFileErrors"/> is the
/// <c>MigratedUnstamped</c> re-verification's own scan (<c>DarlingManagedPostgres.MigrateManagedConfAsync</c>),
/// which must skip the same <see cref="DarlingStoreHostProfile.CommandLineOnlyKeys"/> as
/// <see cref="ManagedConfMigrationRunner.VerifyStepB"/> does, for the same reason: an exposed store's
/// <c>listen_addresses</c> is always overridden by the command line, so darling-managed.conf's rendered
/// (always loopback-only) line reports <c>error = "setting could not be applied"</c> on every start even
/// though nothing needs healing. <c>192.0.2.10</c> (RFC 5737) stands in for the store's own address.
/// </summary>
[Fact]
public void FindUnstampedManagedFileErrors_ListenAddressesOverriddenByCommandLine_IsNotAnError()
{
var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName);
var rows = new List<FileSettingRow>
{
new(SourceFile: managedPath, SourceLine: 1, Name: "listen_addresses", Setting: "127.0.0.1", Applied: false, Error: "setting could not be applied"),
Applied("work_mem", "16MB", file: managedPath, line: 2),
};

var (hasError, mismatchedKeys) = ManagedConfMigrationRunner.FindUnstampedManagedFileErrors(rows);

Assert.False(hasError);
Assert.Empty(mismatchedKeys);
}

/// <summary>Pin: the same skip for <c>port</c> — also command-line-owned
/// (<see cref="DarlingStoreHostProfile.CommandLineOnlyKeys"/>).</summary>
[Fact]
public void FindUnstampedManagedFileErrors_PortOverriddenByCommandLine_IsNotAnError()
{
var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName);
var rows = new List<FileSettingRow>
{
new(SourceFile: managedPath, SourceLine: 1, Name: "port", Setting: "5555", Applied: false, Error: "setting could not be applied"),
Applied("work_mem", "16MB", file: managedPath, line: 2),
};

var (hasError, mismatchedKeys) = ManagedConfMigrationRunner.FindUnstampedManagedFileErrors(rows);

Assert.False(hasError);
Assert.Empty(mismatchedKeys);
}

/// <summary>Pin: a REAL error row on another key (not command-line-owned) still reports as an error,
/// with that key named — the skip is narrow, not a blanket "ignore darling-managed.conf errors".</summary>
[Fact]
public void FindUnstampedManagedFileErrors_RealErrorOnOtherKey_StillReportsError()
{
var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName);
var rows = new List<FileSettingRow>
{
new(SourceFile: managedPath, SourceLine: 1, Name: "listen_addresses", Setting: "127.0.0.1", Applied: false, Error: "setting could not be applied"),
new(SourceFile: managedPath, SourceLine: 2, Name: "work_mem", Setting: "16MB", Applied: false, Error: "invalid value"),
};

var (hasError, mismatchedKeys) = ManagedConfMigrationRunner.FindUnstampedManagedFileErrors(rows);

Assert.True(hasError);
Assert.DoesNotContain("listen_addresses", mismatchedKeys);
Assert.Contains("work_mem", mismatchedKeys);
}

/// <summary>Pin: a store with a configured network endpoint always starts PostgreSQL with
/// <c>listen_addresses</c> forced onto the command line (<see cref="DarlingManagedPostgres.BuildServerRuntimeOptions"/>).
/// The rendered <c>darling-managed.conf</c> line stays loopback-only, so PostgreSQL reports THAT file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3037,23 +3037,15 @@ outcome is now unknown. Keep the prior non-null outcome rather than erasing it w
same here: migrated, no pending file, stale stamp.
Re-verify against what is on disk NOW: no new error row may come from darling-managed.conf
relative to the file's own current bytes — the file itself is the ground truth once no
pending snapshot survives to compare against. */
pending snapshot survives to compare against. Except DarlingStoreHostProfile.CommandLineOnlyKeys
(port, listen_addresses): an exposed store always starts PostgreSQL with both forced onto
the pg_ctl command line, which outranks the file unconditionally, so the rendered
(always loopback-only) listen_addresses line reports an error row here on every start of
an exposed store even though nothing is actually wrong — same trap and same fix as
ManagedConfMigrationRunner.VerifyStepB below. */
var rows = await snapshot(cancellationToken);
var managedConfPath = Path.Combine(_dataDirectory, ManagedConfFile.FileName);
var newErrorFromManagedFile = false;
var mismatchedKeys = new List<string>();
foreach (var row in rows)
{
if (row.Error is not null && row.SourceFile is not null
&& string.Equals(Path.GetFileName(row.SourceFile), ManagedConfFile.FileName, StringComparison.OrdinalIgnoreCase))
{
newErrorFromManagedFile = true;
if (row.Name is not null)
{
mismatchedKeys.Add(row.Name);
}
}
}
var (newErrorFromManagedFile, mismatchedKeys) = ManagedConfMigrationRunner.FindUnstampedManagedFileErrors(rows);

if (newErrorFromManagedFile)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,49 @@ private static ManagedConfMigrationOutcome CompareAndFinish(
ManagedConfVerificationStatus.Failed, mismatches, backupPath, ManagedConfMigrationStep.A);
}

/// <summary>
/// The <see cref="ManagedConfMigrationState.Kind.MigratedUnstamped"/> re-verification's own scan of a
/// fresh <c>pg_file_settings</c> snapshot: every error row whose <c>sourcefile</c> names
/// <see cref="ManagedConfFile.FileName"/>, by <see cref="Path.GetFileName(string)"/> (not a suffix
/// match — a stray <c>old-darling-managed.conf</c> in an include directory is a different file), EXCEPT
/// <see cref="DarlingStoreHostProfile.CommandLineOnlyKeys"/> (<c>port</c>, <c>listen_addresses</c>) — the
/// same skip <see cref="VerifyStepB"/> applies, and for the same reason: an exposed store's command line
/// always outranks the file for both keys, so PostgreSQL reports the rendered (always loopback-only) file
/// row with <c>error = 'setting could not be applied'</c> on every start, never a real mismatch. Returns
/// the matched keys (empty when clean, whether because nothing matched or every match was named for a
/// skipped key) so the caller can log and build the <c>Failed</c> outcome exactly as before this method
/// existed — this is a pure extraction, not a behavior change beyond the skip. <c>HasError</c> is true
/// whenever a genuine (non-skipped) error row was found, INCLUDING one with no <c>Name</c> — the
/// pre-extraction code failed on that row too, even though it never had a key to name.
/// </summary>
internal static (bool HasError, IReadOnlyList<string> MismatchedKeys) FindUnstampedManagedFileErrors(
IReadOnlyList<FileSettingRow> rows)
{
var hasError = false;
var mismatchedKeys = new List<string>();
foreach (var row in rows)
{
if (row.Error is null || row.SourceFile is null
|| !string.Equals(Path.GetFileName(row.SourceFile), ManagedConfFile.FileName, StringComparison.OrdinalIgnoreCase))
{
continue;
}

if (row.Name is not null && Array.IndexOf(DarlingStoreHostProfile.CommandLineOnlyKeys, row.Name) >= 0)
{
continue;
}

hasError = true;
if (row.Name is not null)
{
mismatchedKeys.Add(row.Name);
}
}

return (hasError, mismatchedKeys);
}

/// <summary>
/// Step B: after a normal derivation has already rendered and written
/// <c>darling-managed.conf</c>, checks that <paramref name="rows"/> (a fresh <c>pg_file_settings</c>
Expand Down
Loading