Skip to content

Commit 057ad43

Browse files
committed
add more tests
1 parent 28c5da4 commit 057ad43

File tree

5 files changed

+445
-84
lines changed

5 files changed

+445
-84
lines changed

modules/repository-s3/qa/insecure-credentials/src/test/java/org/elasticsearch/repositories/s3/RepositoryCredentialsTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ S3Service s3Service(
264264
ProjectResolver projectResolver,
265265
ResourceWatcherService resourceWatcherService
266266
) {
267-
return new ProxyS3Service(environment, nodeSettings, resourceWatcherService);
267+
return new ProxyS3Service(environment, clusterService, projectResolver, resourceWatcherService);
268268
}
269269

270270
/**

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3PerProjectClientManager.java

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import org.elasticsearch.cluster.ClusterStateListener;
1616
import org.elasticsearch.cluster.metadata.ProjectId;
1717
import org.elasticsearch.cluster.metadata.ProjectMetadata;
18-
import org.elasticsearch.cluster.metadata.RepositoryMetadata;
1918
import org.elasticsearch.common.settings.ProjectSecrets;
2019
import org.elasticsearch.common.settings.Settings;
2120
import org.elasticsearch.common.util.Maps;
@@ -41,8 +40,9 @@
4140
public class S3PerProjectClientManager implements ClusterStateListener {
4241

4342
private static final Logger logger = LogManager.getLogger(S3PerProjectClientManager.class);
43+
private static final String S3_SETTING_PREFIX = "s3.";
4444

45-
private final Settings settings;
45+
private final Settings nodeS3Settings;
4646
private final Function<S3ClientSettings, AmazonS3Reference> clientBuilder;
4747
private final Executor executor;
4848
// A map of projectId to clients holder. Adding to and removing from the map happen only in the cluster state listener thread.
@@ -51,7 +51,10 @@ public class S3PerProjectClientManager implements ClusterStateListener {
5151
private volatile SubscribableListener<Void> clientsCloseListener = null;
5252

5353
S3PerProjectClientManager(Settings settings, Function<S3ClientSettings, AmazonS3Reference> clientBuilder, Executor executor) {
54-
this.settings = settings;
54+
this.nodeS3Settings = Settings.builder()
55+
.put(settings.getByPrefix(S3_SETTING_PREFIX), false) // not use any cluster scoped secrets
56+
.normalizePrefix(S3_SETTING_PREFIX)
57+
.build();
5558
this.clientBuilder = clientBuilder;
5659
this.executor = executor;
5760
this.projectClientsHolders = new ConcurrentHashMap<>();
@@ -66,36 +69,48 @@ public void clusterChanged(ClusterChangedEvent event) {
6669
final Map<ProjectId, ProjectMetadata> currentProjects = event.state().metadata().projects();
6770

6871
final var updatedPerProjectClients = new HashMap<ProjectId, ClientsHolder>();
72+
final List<ClientsHolder> clientsHoldersToClose = new ArrayList<>();
6973
for (var project : currentProjects.values()) {
7074
final ProjectSecrets projectSecrets = project.custom(ProjectSecrets.TYPE);
71-
if (projectSecrets == null) {
72-
// This can only happen when a node restarts, it will be processed again when file settings are loaded
75+
// Project secrets can be null when node restarts. It may not have s3 credentials if s3 is not used.
76+
if (projectSecrets == null || projectSecrets.getSettingNames().stream().noneMatch(key -> key.startsWith("s3."))) {
77+
// Most likely there won't be any existing client, but attempt to remove it anyway just in case
78+
final ClientsHolder removed = projectClientsHolders.remove(project.id());
79+
if (removed != null) {
80+
clientsHoldersToClose.add(removed);
81+
}
7382
continue;
7483
}
84+
7585
final Settings currentSettings = Settings.builder()
76-
// merge with static settings such as max retries etc, exclude secure settings
86+
// merge with static settings such as max retries etc
7787
// TODO: We may need to update this if per-project settings decide to support hierarchical overrides
78-
.put(settings, false) // do not fallback to cluster scoped secrets
88+
.put(nodeS3Settings)
7989
.setSecureSettings(projectSecrets.getSettings())
8090
.build();
8191
final Map<String, S3ClientSettings> clientSettings = S3ClientSettings.load(currentSettings)
8292
.entrySet()
8393
.stream()
84-
// Skip project clients that have no credentials configured. This should not happen in serverless since all clients should
85-
// have credentials configured. But it is safer to skip them.
94+
// Skip project clients that have no credentials configured. This should not happen in serverless.
95+
// But it is safer to skip them and is also a more consistent behaviour with the cases when
96+
// project secrets are not present.
8697
.filter(entry -> entry.getValue().credentials != null)
8798
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
8899

89-
// TODO: clientSettings should not be empty, i.e. there should be at least one client configured
90-
// Maybe log a warning if it is empty and continue. The project will not have usable client but that is probably ok.
100+
if (clientSettings.isEmpty()) {
101+
// clientSettings should not be empty, i.e. there should be at least one client configured.
102+
// But if it does somehow happen, log a warning and continue. The project will not have usable client but that is ok.
103+
logger.warn("Skipping project [{}] with no client settings", project.id());
104+
continue;
105+
}
91106

92-
// TODO: Building and comparing the whole S3ClientSettings may be inefficient, we could just compare the relevant secrets
107+
// TODO: If performance is an issue, we may consider comparing just the relevant project secrets for new or updated clients
108+
// and avoid building the clientSettings
93109
if (newOrUpdated(project.id(), clientSettings)) {
94-
updatedPerProjectClients.put(project.id(), new ClientsHolder(clientSettings));
110+
updatedPerProjectClients.put(project.id(), new ClientsHolder(project.id(), clientSettings));
95111
}
96112
}
97113

98-
final List<ClientsHolder> clientsHoldersToClose = new ArrayList<>();
99114
// Updated projects
100115
for (var projectId : updatedPerProjectClients.keySet()) {
101116
final var old = projectClientsHolders.put(projectId, updatedPerProjectClients.get(projectId));
@@ -133,12 +148,12 @@ private void closeClientsAsync(List<ClientsHolder> clientsHoldersToClose, Action
133148
});
134149
}
135150

136-
public AmazonS3Reference client(ProjectId projectId, RepositoryMetadata repositoryMetadata) {
151+
public AmazonS3Reference client(ProjectId projectId, String clientName) {
152+
assert projectId != null && ProjectId.DEFAULT.equals(projectId) == false : projectId;
137153
final var clientsHolder = projectClientsHolders.get(projectId);
138154
if (clientsHolder == null) {
139-
throw new IllegalArgumentException("project [" + projectId + "] does not exist");
155+
throw new IllegalArgumentException("no s3 client is configured for project [" + projectId + "]");
140156
}
141-
final String clientName = S3Repository.CLIENT_NAME.get(repositoryMetadata.settings());
142157
return clientsHolder.client(clientName);
143158
}
144159

@@ -147,6 +162,7 @@ public AmazonS3Reference client(ProjectId projectId, RepositoryMetadata reposito
147162
* All clients for the project are closed and will be recreated on next access, also similar to S3Service#releaseCachedClients
148163
*/
149164
public void releaseProjectClients(ProjectId projectId) {
165+
assert projectId != null && ProjectId.DEFAULT.equals(projectId) == false : projectId;
150166
final var old = projectClientsHolders.get(projectId);
151167
if (old != null) {
152168
old.clearCache();
@@ -197,11 +213,13 @@ private boolean newOrUpdated(ProjectId projectId, Map<String, S3ClientSettings>
197213
*/
198214
final class ClientsHolder implements Closeable {
199215
private final AtomicBoolean closed = new AtomicBoolean(false);
216+
private final ProjectId projectId;
200217
private final Map<String, S3ClientSettings> clientSettings;
201218
// Client name -> client reference
202219
private volatile Map<String, AmazonS3Reference> clientsCache = Collections.emptyMap();
203220

204-
ClientsHolder(Map<String, S3ClientSettings> clientSettings) {
221+
ClientsHolder(ProjectId projectId, Map<String, S3ClientSettings> clientSettings) {
222+
this.projectId = projectId;
205223
this.clientSettings = clientSettings;
206224
}
207225

@@ -219,7 +237,7 @@ AmazonS3Reference client(String clientName) {
219237
}
220238
final var settings = clientSettings.get(clientName);
221239
if (settings == null) {
222-
throw new IllegalArgumentException("client [" + clientName + "] does not exist");
240+
throw new IllegalArgumentException("s3 client [" + clientName + "] does not exist for project [" + projectId + "]");
223241
}
224242
synchronized (this) {
225243
final var existing = clientsCache.get(clientName);

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Service.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,12 @@ public AmazonS3Reference client(@Nullable ProjectId projectId, RepositoryMetadat
222222
// Multi-Project is enabled and we are retrieving a client for the cluster level blobstore
223223
return client(repositoryMetadata);
224224
} else {
225-
return s3PerProjectClientManager.client(projectId, repositoryMetadata);
225+
final String clientName = S3Repository.CLIENT_NAME.get(repositoryMetadata.settings());
226+
return s3PerProjectClientManager.client(projectId, clientName);
226227
}
227228
}
228229

229-
private AmazonS3Reference buildClientReference(final S3ClientSettings clientSettings) {
230+
protected AmazonS3Reference buildClientReference(final S3ClientSettings clientSettings) {
230231
final SdkHttpClient httpClient = buildHttpClient(clientSettings, getCustomDnsResolver());
231232
Releasable toRelease = httpClient::close;
232233
try {

0 commit comments

Comments
 (0)