From 97610f3fe1a7950bbc2f0f1db5945c172f8cb005 Mon Sep 17 00:00:00 2001 From: Paul Millar Date: Thu, 1 Aug 2024 09:18:54 +0200 Subject: [PATCH] Add caching layer to avoid hammering ALISE service --- .../org/dcache/gplazma/alise/AlisePlugin.java | 10 ++- .../gplazma/alise/CachingLookupAgent.java | 88 +++++++++++++++++++ .../org/dcache/gplazma/alise/LookupAgent.java | 6 ++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/CachingLookupAgent.java diff --git a/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/AlisePlugin.java b/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/AlisePlugin.java index fda617beae0..b5bd9851b2f 100644 --- a/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/AlisePlugin.java +++ b/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/AlisePlugin.java @@ -79,8 +79,11 @@ private static LookupAgent buildLookupAgent(Properties config) var apikey = getRequiredProperty(config,"gplazma.alise.apikey"); var target = getRequiredProperty(config, "gplazma.alise.target"); var timeout = getRequiredProperty(config, "gplazma.alise.timeout"); + LookupAgent alise = new AliseLookupAgent(endpoint, target, apikey, timeout); - return new AliseLookupAgent(endpoint, target, apikey, timeout); + LookupAgent cache = new CachingLookupAgent(alise); + + return cache; } public AlisePlugin(Properties config) throws URISyntaxException { @@ -174,4 +177,9 @@ public void map(Set principals) throws AuthenticationException { .flatMap(o -> o.orElseThrow().stream()) .forEach(principals::add); } + + @Override + public void stop() throws Exception { + lookupAgent.shutdown(); + } } diff --git a/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/CachingLookupAgent.java b/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/CachingLookupAgent.java new file mode 100644 index 00000000000..7532705f470 --- /dev/null +++ b/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/CachingLookupAgent.java @@ -0,0 +1,88 @@ +/* + * dCache - http://www.dcache.org/ + * + * Copyright (C) 2024 Deutsches Elektronen-Synchrotron + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.dcache.gplazma.alise; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableFutureTask; +import java.security.Principal; +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import org.dcache.util.BoundedCachedExecutor; +import org.dcache.util.Result; + +import static java.util.Objects.requireNonNull; + +/** + * A simple caching layer to prevents dCache from hammering ALISE, if there are + * repeated requests for the same identity. + * + * The primary use-case is when there are multiple clients using dCache with + * different (distinct) OIDC tokens but that have the same underlying identity + * ('iss' and 'sub' claim). The dCache doors will forward each OIDC token to + * gPlazma as the bearer tokens are distinct, but repeated calls to ALISE for + * the same identity makes no sense. + */ +public class CachingLookupAgent implements LookupAgent { + + private final LookupAgent inner; + private final ExecutorService executor = new BoundedCachedExecutor(5); + + private final LoadingCache, String>> lookupResults = CacheBuilder.newBuilder() + .maximumSize(1_000) + .refreshAfterWrite(5, TimeUnit.SECONDS) + .expireAfterWrite(60, TimeUnit.SECONDS) + .build(new CacheLoader, String>>() { + @Override + public Result, String> load(Identity identity) { + return inner.lookup(identity); + } + + @Override + public ListenableFuture, String>> reload(Identity identity, Result,String> prevResult) { + var task = ListenableFutureTask.create(() -> inner.lookup(identity)); + executor.execute(task); + return task; + } + }); + + public CachingLookupAgent(LookupAgent inner) { + this.inner = requireNonNull(inner); + } + + @Override + public Result, String> lookup(Identity identity) { + try { + return lookupResults.get(identity); + } catch (ExecutionException e) { + Throwable reported = e.getCause() == null ? e : e.getCause(); + return Result.failure("Cache lookup failed: " + reported); + } + } + + @Override + public void shutdown() { + executor.shutdown(); + inner.shutdown(); + } +} diff --git a/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/LookupAgent.java b/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/LookupAgent.java index 93e96914baf..488814b5847 100644 --- a/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/LookupAgent.java +++ b/modules/gplazma2-alise/src/main/java/org/dcache/gplazma/alise/LookupAgent.java @@ -38,4 +38,10 @@ public interface LookupAgent { * principals that identify the user, if failure a suitable error message. */ public Result,String> lookup(Identity identity); + + /** + * A method called when the plugin is shutting down. Should release + * established resources (e.g., shutting down threads). + */ + default public void shutdown() {} }