Code review comment: I misread this, I thought it was happening at runtime. It's not ideal that it connects to each tenant sequentially but it is probably fine for now, sorry for the noise.

Code before review:
private TenantConfigContext createTenantContext(Vertx vertx, OidcTenantConfig oidcConfig) {
OAuth2ClientOptions options = new OAuth2ClientOptions();
if (!oidcConfig.getAuthServerUrl().isPresent()) {
return null;
}
options.setSite(oidcConfig.getAuthServerUrl().get());
if (oidcConfig.getIntrospectionPath().isPresent()) {
options.setIntrospectionPath(oidcConfig.getIntrospectionPath().get());
}
if (oidcConfig.getJwksPath().isPresent()) {
options.setJwkPath(oidcConfig.getJwksPath().get());
}
if (oidcConfig.getClientId().isPresent()) {
options.setClientID(oidcConfig.getClientId().get());
}
if (oidcConfig.getCredentials().secret.isPresent()) {
options.setClientSecret(oidcConfig.getCredentials().secret.get());
}
if (oidcConfig.getPublicKey().isPresent()) {
options.addPubSecKey(new PubSecKeyOptions()
.setAlgorithm("RS256")
.setPublicKey(oidcConfig.getPublicKey().get()));
}
if (oidcConfig.getToken().issuer.isPresent()) {
options.setValidateIssuer(false);
}
if (oidcConfig.getToken().getExpirationGrace().isPresent()) {
JWTOptions jwtOptions = new JWTOptions();
jwtOptions.setLeeway(oidcConfig.getToken().getExpirationGrace().get());
options.setJWTOptions(jwtOptions);
}
final long connectionDelayInSecs = oidcConfig.getConnectionDelay().isPresent()
? oidcConfig.getConnectionDelay().get().toMillis() / 1000
: 0;
final long connectionRetryCount = connectionDelayInSecs > 1 ? connectionDelayInSecs / 2 : 1;
if (connectionRetryCount > 1) {
LOG.infof("Connecting to IDP for up to %d times every 2 seconds", connectionRetryCount);
}
OAuth2Auth auth = null;
for (long i = 0; i < connectionRetryCount; i++) {
try {
CompletableFuture<OAuth2Auth> cf = new CompletableFuture<>();
KeycloakAuth.discover(vertx, options, new Handler<AsyncResult<OAuth2Auth>>() {
@Override
public void handle(AsyncResult<OAuth2Auth> event) {
if (event.failed()) {
cf.completeExceptionally(toOidcException(event.cause()));
} else {
cf.complete(event.result());
}
}
});
auth = cf.join();
break;
} catch (Throwable throwable) {
while (throwable instanceof CompletionException && throwable.getCause() != null) {
throwable = throwable.getCause();
}
if (throwable instanceof OIDCException) {
if (i + 1 < connectionRetryCount) {
CompletableFuture.runAsync(
new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException iex) {
}
}
})
.join();
} else {
throw (OIDCException) throwable;
}
} else {
throw new OIDCException(throwable);
}
}
}
return new TenantConfigContext(auth, oidcConfig);
}

Line under review: .join();

Code after addressing the review:
private TenantConfigContext createTenantContext(Vertx vertx, OidcTenantConfig oidcConfig) {
OAuth2ClientOptions options = new OAuth2ClientOptions();
if (!oidcConfig.getAuthServerUrl().isPresent()) {
return null;
}
options.setSite(oidcConfig.getAuthServerUrl().get());
if (oidcConfig.getIntrospectionPath().isPresent()) {
options.setIntrospectionPath(oidcConfig.getIntrospectionPath().get());
}
if (oidcConfig.getJwksPath().isPresent()) {
options.setJwkPath(oidcConfig.getJwksPath().get());
}
if (oidcConfig.getClientId().isPresent()) {
options.setClientID(oidcConfig.getClientId().get());
}
if (oidcConfig.getCredentials().secret.isPresent()) {
options.setClientSecret(oidcConfig.getCredentials().secret.get());
}
if (oidcConfig.getPublicKey().isPresent()) {
options.addPubSecKey(new PubSecKeyOptions()
.setAlgorithm("RS256")
.setPublicKey(oidcConfig.getPublicKey().get()));
}
if (oidcConfig.getToken().issuer.isPresent()) {
options.setValidateIssuer(false);
}
if (oidcConfig.getToken().getExpirationGrace().isPresent()) {
JWTOptions jwtOptions = new JWTOptions();
jwtOptions.setLeeway(oidcConfig.getToken().getExpirationGrace().get());
options.setJWTOptions(jwtOptions);
}
final long connectionDelayInSecs = oidcConfig.getConnectionDelay().isPresent()
? oidcConfig.getConnectionDelay().get().toMillis() / 1000
: 0;
final long connectionRetryCount = connectionDelayInSecs > 1 ? connectionDelayInSecs / 2 : 1;
if (connectionRetryCount > 1) {
LOG.infof("Connecting to IDP for up to %d times every 2 seconds", connectionRetryCount);
}
OAuth2Auth auth = null;
for (long i = 0; i < connectionRetryCount; i++) {
try {
CompletableFuture<OAuth2Auth> cf = new CompletableFuture<>();
KeycloakAuth.discover(vertx, options, new Handler<AsyncResult<OAuth2Auth>>() {
@Override
public void handle(AsyncResult<OAuth2Auth> event) {
if (event.failed()) {
cf.completeExceptionally(toOidcException(event.cause()));
} else {
cf.complete(event.result());
}
}
});
auth = cf.join();
break;
} catch (Throwable throwable) {
while (throwable instanceof CompletionException && throwable.getCause() != null) {
throwable = throwable.getCause();
}
if (throwable instanceof OIDCException) {
if (i + 1 < connectionRetryCount) {
try {
Thread.sleep(2000);
} catch (InterruptedException iex) {
}
} else {
throw (OIDCException) throwable;
}
} else {
throw new OIDCException(throwable);
}
}
}
return new TenantConfigContext(auth, oidcConfig);
}