diff --git a/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs b/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs index 6a9ce820c..9eae4c5b3 100644 --- a/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs +++ b/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs @@ -503,6 +503,94 @@ public void VerifyStepB_NewErrorFromManagedFile_Fails() Assert.Contains("work_mem", outcome.MismatchedKeys); } + /// Pin: a store with a configured network endpoint always starts PostgreSQL with + /// listen_addresses forced onto the command line (). + /// The rendered darling-managed.conf line stays loopback-only, so PostgreSQL reports THAT file + /// row with error = "setting could not be applied" once its command-line value differs — + /// confirmed against a live PostgreSQL 18 instance. + /// must not treat that as a mismatch: listen_addresses is skipped, but a REAL mismatch on another + /// key in the same batch still fails. + [Fact] + public void VerifyStepB_ListenAddressesOverriddenByCommandLine_IsNotAMismatch_OtherKeyStillFails() + { + var rendered = "listen_addresses = '127.0.0.1'\nwork_mem = '16MB'\n"; + WriteManaged(rendered); + + var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName); + var rows = new List + { + 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: "8MB", Applied: true, Error: null), + }; + + var outcome = ManagedConfMigrationRunner.VerifyStepB(_dataDir, rows, rendered, previousText: null); + + Assert.Equal(ManagedConfVerificationStatus.Failed, outcome.Status); + Assert.DoesNotContain("listen_addresses", outcome.MismatchedKeys); + Assert.Contains("work_mem", outcome.MismatchedKeys); + } + + /// Pin: when EVERY other key matches and the only rendered key that doesn't is + /// listen_addresses — overridden by the command line, same as above — Step B now completes + /// (Verified), writes the verified stamp against the rendered text, and leaves the managed file + /// in place (no restore of ). Before this fix, this row alone drove Step + /// B to Failed on every start of any store with a configured network endpoint. The row's exact + /// shape (setting reads the file's rendered value, not the command line's; error reads + /// "setting could not be applied"; applied is false) was captured from a real + /// pg_file_settings row on a PostgreSQL 18 container started with a command-line + /// listen_addresses set to loopback plus the container's own address (docker run + /// timescale/timescaledb:2.30.1-pg18 -c listen_addresses=127.0.0.1,<container address>, with an + /// included conf file rendering the product's usual loopback-only listen_addresses = '127.0.0.1' + /// line), then querying directly against it. The address + /// below (192.0.2.10) is the RFC 5737 documentation range, standing in for the container's own + /// address — the captured row shape does not depend on which address is used. + [Fact] + public void VerifyStepB_OnlyListenAddressesOverriddenByCommandLine_Verifies() + { + var rendered = "listen_addresses = '127.0.0.1'\nwork_mem = '16MB'\nmax_connections = '200'\n"; + WriteManaged(rendered); + + var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName); + var rows = new List + { + 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), + Applied("max_connections", "200", file: managedPath, line: 3), + }; + + var previousText = "work_mem = '8MB'\n"; + + var outcome = ManagedConfMigrationRunner.VerifyStepB(_dataDir, rows, rendered, previousText); + + Assert.Equal(ManagedConfVerificationStatus.Verified, outcome.Status); + Assert.Equal(ManagedConfMigrationStep.B, outcome.Step); + Assert.True(ManagedConfMigrationSteps.IsVerified(_dataDir)); + Assert.Equal(rendered, File.ReadAllText(managedPath)); + } + + /// Pin: the same completion for port — also command-line-owned + /// () — whose file row is overridden the same + /// way when the command line pins a different port than the rendered file line. + [Fact] + public void VerifyStepB_OnlyPortOverriddenByCommandLine_Verifies() + { + var rendered = "port = '5555'\nwork_mem = '16MB'\n"; + WriteManaged(rendered); + + var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName); + var rows = new List + { + 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 outcome = ManagedConfMigrationRunner.VerifyStepB(_dataDir, rows, rendered, previousText: null); + + Assert.Equal(ManagedConfVerificationStatus.Verified, outcome.Status); + Assert.Equal(ManagedConfMigrationStep.B, outcome.Step); + Assert.True(ManagedConfMigrationSteps.IsVerified(_dataDir)); + } + /// Pin (#4336): the exact change-log line for two changed keys. [Fact] public void FormatStepBChangeLog_TwoKeys_ExactLine() diff --git a/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs b/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs index d861703cf..42378eadd 100644 --- a/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs +++ b/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs @@ -304,9 +304,19 @@ private static ManagedConfMigrationOutcome CompareAndFinish( /// (#4336; , not a suffix match — a stray old-darling-managed.conf /// in an include directory is a different file). A row's /// applied may be false — an operator line below the include can legitimately override it; only - /// the value and the absence of an error matter here. All keys match: stamp the new text as verified. Any - /// key fails: restore (when there was one) so the old stamp matches - /// again, and report Failed. + /// the value and the absence of an error matter here. + /// + /// (port, listen_addresses) are + /// skipped entirely here — never looked up in 's keys, never compared — + /// because BuildServerRuntimeOptions always starts PostgreSQL with both on the pg_ctl -o "-c + /// ..." command line, which outranks the conf file unconditionally (confirmed live). An exposed + /// store's running listen_addresses carries the network IP the rendered file line never does + /// (the file always renders loopback-only, #4215/BuildConfAppend); PostgreSQL's own pg_file_settings + /// then reports that file row with error = 'setting could not be applied' — not merely + /// applied = false — because the value differs from what's actually in force, same as any + /// operator line the command line has already overridden. Comparing this key here would fail Step B on + /// every start for any store with a configured network endpoint, even though the store's listener is + /// exactly what it should be; there is nothing this file could ever own for either key. /// internal static ManagedConfMigrationOutcome VerifyStepB( string dataDir, @@ -335,6 +345,11 @@ internal static ManagedConfMigrationOutcome VerifyStepB( var mismatchedKeys = new List(); foreach (var (_, name, value) in DarlingManagedPostgres.ParseConfText(renderedText)) { + if (Array.IndexOf(DarlingStoreHostProfile.CommandLineOnlyKeys, name) >= 0) + { + continue; + } + var ok = byKey.TryGetValue(name, out var candidates) && candidates.Exists(r => r.Error is null && string.Equals(r.Setting, value, StringComparison.Ordinal)); if (!ok)