smolnar82 commented on code in PR #1456:
URL: https://github.com/apache/knox/pull/1456#discussion_r4347246022


##########
gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/JdbcTrustedOidcIssuerServiceTest.java:
##########
@@ -493,10 +497,274 @@ public void testConcurrentInitDoesNotReinitialize() 
throws Exception {
         sentinel, FieldUtils.readField(service, "database", true));
   }
 
+  // ------------------------------------------------------------------
+  // Registry snapshot TTL (HA eventual consistency)
+  // ------------------------------------------------------------------
+  // Direct JDBC writes stand in for another Knox node writing to the shared 
table. With a TTL of
+  // 10s the effective (jittered) TTL is in [9s, 10s] and the background 
refresh is due after at
+  // most 5s, so +6s means "refresh due, not expired" and +11s means "expired".
+
+  private static final int TTL_SECS = 10;
+
+  @Test
+  public void testRemoteRegistrationVisibleAfterBackgroundRefresh() throws 
Exception {
+    final AtomicLong now = new AtomicLong();
+    final JdbcTrustedOidcIssuerService ttlService = ttlService(TTL_SECS, now);
+    insertRow("https://remote.example.com";, false, Instant.now());
+
+    assertFalse("A fresh snapshot is served without hitting the DB",
+        ttlService.isTrusted("https://remote.example.com";));
+
+    advance(now, 6);
+    ttlService.isTrusted("https://remote.example.com";); // triggers the 
background refresh, served stale
+    assertTrue("Remote registration must be visible once the background 
refresh completed",
+        ttlService.isTrusted("https://remote.example.com";));
+  }
+
+  @Test
+  public void testRemoteDeregistrationNeverServedAfterTtl() throws Exception {
+    final AtomicLong now = new AtomicLong();
+    final JdbcTrustedOidcIssuerService ttlService = ttlService(TTL_SECS, now);
+    ttlService.register(issuer("https://issuer.example.com";, false));
+    deleteRow("https://issuer.example.com";);
+
+    // No read in between, so no background refresh ran: the expired snapshot 
must not be served
+    advance(now, 11);
+    assertFalse("A revoked issuer must not be trusted once the TTL elapsed",
+        ttlService.isTrusted("https://issuer.example.com";));
+  }
+
+  @Test
+  public void testNonPositiveTtlFallsBackToDefault() throws Exception {
+    final AtomicLong now = new AtomicLong();
+    final JdbcTrustedOidcIssuerService ttlService = ttlService(0, now);
+    insertRow("https://remote.example.com";, false, Instant.now());
+
+    // Default TTL is 60s: effective TTL in [54s, 60s], refresh due after at 
least 27s
+    advance(now, 25);
+    ttlService.isTrusted("https://remote.example.com";);
+    assertFalse(ttlService.isTrusted("https://remote.example.com";));
+
+    advance(now, 36);
+    assertTrue(ttlService.isTrusted("https://remote.example.com";));
+  }
+
+  /**
+   * DB outage: the snapshot is served stale until the TTL, a failed refresh 
neither extends the
+   * TTL nor is retried on every request, then the registry fails closed and 
recovers once the DB
+   * is readable again.
+   */
+  @Test
+  public void testDatabaseOutageServesStaleThenFailsClosedThenRecovers() 
throws Exception {
+    final AtomicLong now = new AtomicLong();
+    final JdbcTrustedOidcIssuerService ttlService = ttlService(TTL_SECS, now);
+    ttlService.register(issuer("https://issuer.example.com";, false));
+    final TrustedOidcIssuerDatabase realDatabase =
+        (TrustedOidcIssuerDatabase) FieldUtils.readField(ttlService, 
"database", true);
+
+    final TrustedOidcIssuerDatabase failingDb = 
EasyMock.createMock(TrustedOidcIssuerDatabase.class);
+    EasyMock.expect(failingDb.selectAll()).andThrow(new SQLException("database 
is down")).times(2);
+    EasyMock.replay(failingDb);
+    useDatabase(ttlService, failingDb);
+
+    advance(now, 6);
+    for (int i = 0; i < 5; i++) {
+      assertTrue("The cached snapshot must be served while it is younger than 
the TTL",
+          ttlService.isTrusted("https://issuer.example.com";));
+    }
+
+    advance(now, 5); // 11s after the last successful load, although the 
failed refresh ran at 6s
+    assertFalse("The registry must fail closed once the snapshot expired",
+        ttlService.isTrusted("https://issuer.example.com";));
+    assertTrue(ttlService.list().isEmpty());
+    assertTrue(registrySnapshotLoader(ttlService).isFailedClosed());
+    // One failed background refresh at 6s plus one failed load at 11s: no 
per-request DB retries
+    EasyMock.verify(failingDb);
+
+    useDatabase(ttlService, realDatabase);
+    advance(now, 6);

Review Comment:
   Yes. The fail-closed snapshot is a normal cache entry stamped at the failure 
time, so it follows the same schedule: retried in the background after TTL/2, 
reloaded synchronously after TTL. 
   
   Without the advance it's fresh and is served without hitting the DB. That's 
deliberate: one DB attempt per TTL/2 during an outage, not one per request. 
I'll clarify in `operations.md` that recovery can take up to about TTL/2.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to