1818import org .elasticsearch .common .settings .ProjectSecrets ;
1919import org .elasticsearch .common .settings .Settings ;
2020import org .elasticsearch .common .util .Maps ;
21+ import org .elasticsearch .common .util .concurrent .ConcurrentCollections ;
2122import org .elasticsearch .logging .LogManager ;
2223import org .elasticsearch .logging .Logger ;
2324
2425import java .io .IOException ;
2526import java .util .HashMap ;
2627import java .util .Map ;
27- import java .util .concurrent .ConcurrentHashMap ;
2828import java .util .stream .Collectors ;
2929
3030import static java .util .Collections .emptyMap ;
@@ -41,7 +41,8 @@ public class GoogleCloudStorageClientsManager implements ClusterStateApplier {
4141 GcsRepositoryStatsCollector ,
4242 MeteredStorage ,
4343 IOException > clientBuilder ;
44- private final Map <ProjectId , ClientsHolder > clientHolders ;
44+ private final ClusterClientsHolder clusterClientsHolder = new ClusterClientsHolder ();
45+ private final Map <ProjectId , ClientsHolder > projectClientHolders ;
4546
4647 public GoogleCloudStorageClientsManager (
4748 Settings nodeSettings ,
@@ -54,16 +55,15 @@ public GoogleCloudStorageClientsManager(
5455 .build ();
5556 this .clientBuilder = clientBuilder ;
5657 if (supportsMultipleProjects ) {
57- // If multiple projects are supported, we need to track per-project clients
58- this .clientHolders = new ConcurrentHashMap <>(Map .of (ProjectId .DEFAULT , new ClientsHolder ()));
58+ this .projectClientHolders = ConcurrentCollections .newConcurrentMap ();
5959 } else {
60- // If only a single project is supported, we use a single holder for the default project
61- this .clientHolders = Map .of (ProjectId .DEFAULT , new ClientsHolder ());
60+ this .projectClientHolders = null ;
6261 }
6362 }
6463
6564 @ Override
6665 public void applyClusterState (ClusterChangedEvent event ) {
66+ assert projectClientHolders != null ;
6767 final Map <ProjectId , ProjectMetadata > currentProjects = event .state ().metadata ().projects ();
6868
6969 final var updatedPerProjectClients = new HashMap <ProjectId , ClientsHolder >();
@@ -77,7 +77,7 @@ public void applyClusterState(ClusterChangedEvent event) {
7777 // Project secrets can be null when node restarts. It may not have any s3 credentials if s3 is not in use.
7878 if (projectSecrets == null || projectSecrets .getSettingNames ().stream ().noneMatch (key -> key .startsWith (GCS_SETTING_PREFIX ))) {
7979 // Most likely there won't be any existing client, but attempt to remove it anyway just in case
80- clientHolders .remove (project .id ());
80+ projectClientHolders .remove (project .id ());
8181 continue ;
8282 }
8383
@@ -106,20 +106,20 @@ public void applyClusterState(ClusterChangedEvent event) {
106106 // TODO: If performance is an issue, we may consider comparing just the relevant project secrets for new or updated clients
107107 // and avoid building the clientSettings
108108 if (newOrUpdated (project .id (), clientSettings )) {
109- updatedPerProjectClients .put (project .id (), new ClientsHolder ( ));
109+ updatedPerProjectClients .put (project .id (), new PerProjectClientsHolder ( clientSettings ));
110110 }
111111 }
112112
113113 // Updated projects
114114 for (var projectId : updatedPerProjectClients .keySet ()) {
115115 assert ProjectId .DEFAULT .equals (projectId ) == false ;
116- clientHolders .put (projectId , updatedPerProjectClients .get (projectId ));
116+ projectClientHolders .put (projectId , updatedPerProjectClients .get (projectId ));
117117 }
118118 // Removed projects
119- for (var projectId : clientHolders .keySet ()) {
119+ for (var projectId : projectClientHolders .keySet ()) {
120120 if (currentProjects .containsKey (projectId ) == false ) {
121121 assert ProjectId .DEFAULT .equals (projectId ) == false ;
122- clientHolders .remove (projectId );
122+ projectClientHolders .remove (projectId );
123123 }
124124 }
125125 }
@@ -129,54 +129,53 @@ MeteredStorage client(String clientName, String repositoryName, GcsRepositorySta
129129 return client (ProjectId .DEFAULT , clientName , repositoryName , statsCollector );
130130 }
131131
132- @ Deprecated (forRemoval = true )
133- void refreshAndClearCache (Map <String , GoogleCloudStorageClientSettings > clientsSettings ) {
134- refreshAndClearCache (ProjectId .DEFAULT , clientsSettings );
135- }
136-
137132 @ Deprecated (forRemoval = true )
138133 void closeRepositoryClients (String repositoryName ) {
139134 closeRepositoryClients (ProjectId .DEFAULT , repositoryName );
140135 }
141136
137+ void refreshAndClearCacheForClusterClients (Map <String , GoogleCloudStorageClientSettings > clientsSettings ) {
138+ clusterClientsHolder .refreshAndClearCache (clientsSettings );
139+ }
140+
142141 MeteredStorage client (ProjectId projectId , String clientName , String repositoryName , GcsRepositoryStatsCollector statsCollector )
143142 throws IOException {
144143 return getClientsHolderSafe (projectId ).client (clientName , repositoryName , statsCollector );
145144 }
146145
147- void refreshAndClearCache (ProjectId projectId , Map <String , GoogleCloudStorageClientSettings > clientsSettings ) {
148- getClientsHolderSafe (projectId ).refreshAndClearCache (clientsSettings );
149- }
150-
151146 void closeRepositoryClients (ProjectId projectId , String repositoryName ) {
152147 getClientsHolderSafe (projectId ).closeRepositoryClients (repositoryName );
153148 }
154149
155150 private boolean newOrUpdated (ProjectId projectId , Map <String , GoogleCloudStorageClientSettings > currentClientSettings ) {
156- final var old = clientHolders .get (projectId );
151+ final var old = projectClientHolders .get (projectId );
157152 if (old == null ) {
158153 return true ;
159154 }
160- return currentClientSettings .equals (old .clientSettings ) == false ;
155+ return currentClientSettings .equals (old .getClientSettings () ) == false ;
161156 }
162157
163158 private ClientsHolder getClientsHolderSafe (ProjectId projectId ) {
164- final var clientsHolder = clientHolders .get (projectId );
159+ final var clientsHolder = projectClientHolders .get (projectId );
165160 if (clientsHolder == null ) {
166161 assert ProjectId .DEFAULT .equals (projectId ) == false ;
167162 throw new IllegalArgumentException ("No GCS client is configured for project [" + projectId + "]" );
168163 }
169164 return clientsHolder ;
170165 }
171166
172- class ClientsHolder {
167+ abstract class ClientsHolder {
173168
174- private volatile Map <String , GoogleCloudStorageClientSettings > clientSettings = emptyMap ();
175169 /**
176170 * Dictionary of client instances. Client instances are built lazily from the
177171 * latest settings. Clients are cached by a composite repositoryName key.
178172 */
179- private volatile Map <String , MeteredStorage > clientCache = emptyMap ();
173+ protected volatile Map <String , MeteredStorage > clientCache = emptyMap ();
174+
175+ /**
176+ * Get the current client settings for all clients in this holder.
177+ */
178+ protected abstract Map <String , GoogleCloudStorageClientSettings > getClientSettings ();
180179
181180 /**
182181 * Attempts to retrieve a client from the cache. If the client does not exist it
@@ -205,14 +204,14 @@ MeteredStorage client(final String clientName, final String repositoryName, fina
205204 return existing ;
206205 }
207206
208- final GoogleCloudStorageClientSettings settings = clientSettings .get (clientName );
207+ final GoogleCloudStorageClientSettings settings = getClientSettings () .get (clientName );
209208
210209 if (settings == null ) {
211210 throw new IllegalArgumentException (
212211 "Unknown client name ["
213212 + clientName
214213 + "]. Existing client configs: "
215- + Strings .collectionToDelimitedString (clientSettings .keySet (), "," )
214+ + Strings .collectionToDelimitedString (getClientSettings () .keySet (), "," )
216215 );
217216 }
218217
@@ -223,6 +222,23 @@ MeteredStorage client(final String clientName, final String repositoryName, fina
223222 }
224223 }
225224
225+ synchronized void closeRepositoryClients (String repositoryName ) {
226+ clientCache = clientCache .entrySet ()
227+ .stream ()
228+ .filter (entry -> entry .getKey ().equals (repositoryName ) == false )
229+ .collect (Collectors .toUnmodifiableMap (Map .Entry ::getKey , java .util .Map .Entry ::getValue ));
230+ }
231+ }
232+
233+ final class ClusterClientsHolder extends ClientsHolder {
234+
235+ private volatile Map <String , GoogleCloudStorageClientSettings > clientSettings = emptyMap ();
236+
237+ @ Override
238+ protected Map <String , GoogleCloudStorageClientSettings > getClientSettings () {
239+ return clientSettings ;
240+ }
241+
226242 /**
227243 * Refreshes the client settings and clears the client cache. Subsequent calls to
228244 * {@code GoogleCloudStorageService#client} will return new clients constructed
@@ -234,12 +250,19 @@ synchronized void refreshAndClearCache(Map<String, GoogleCloudStorageClientSetti
234250 this .clientCache = emptyMap ();
235251 this .clientSettings = Maps .ofEntries (clientsSettings .entrySet ());
236252 }
253+ }
237254
238- synchronized void closeRepositoryClients (String repositoryName ) {
239- clientCache = clientCache .entrySet ()
240- .stream ()
241- .filter (entry -> entry .getKey ().equals (repositoryName ) == false )
242- .collect (Collectors .toUnmodifiableMap (Map .Entry ::getKey , Map .Entry ::getValue ));
255+ final class PerProjectClientsHolder extends ClientsHolder {
256+
257+ private final Map <String , GoogleCloudStorageClientSettings > clientSettings ;
258+
259+ PerProjectClientsHolder (Map <String , GoogleCloudStorageClientSettings > clientSettings ) {
260+ this .clientSettings = clientSettings ;
261+ }
262+
263+ @ Override
264+ protected Map <String , GoogleCloudStorageClientSettings > getClientSettings () {
265+ return clientSettings ;
243266 }
244267 }
245268}
0 commit comments