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


##########
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

Review Comment:
   How do you ensure the background refresh completes before the next line? 
Isn't there a race?
   
   And shouldn't this also assertFalse on the result?



##########
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:
   Why do you need to advance? The limit on a failed closed db is still ttl/2 
to retry?



##########
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);
+    ttlService.isTrusted("https://issuer.example.com";); // triggers the 
background refresh

Review Comment:
   This should still fail, right, so assert on return value.



##########
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);

Review Comment:
   If there is no request within the right timeframe, then the refresh never 
happens and the next request can fail for TTL. Is it not possible to just have 
a background thread running so every TTL/2 requests it refreshes instead? Then 
it doesn't have to be inline with logic.
   
   I thought it was never synchronously fetched -- if it were then if there 
were a few concurrent requests after the timeout then it might burst load.



-- 
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