Skip to content

Commit a6a1389

Browse files
authored
Use client settings in repository-gcs (#28575)
Similarly to what has been done for s3 and azure, this commit removes the repository settings `application_name` and `connect/read_timeout` in favor of client settings. It introduce a GoogleCloudStorageClientSettings class (similar to S3ClientSettings) and a bunch of unit tests for that, it aligns the documentation to be more coherent with the S3 one, it documents the connect/read timeouts that were not documented at all and also adds a new client setting that allows to define a custom endpoint.
1 parent daf430c commit a6a1389

File tree

11 files changed

+589
-240
lines changed

11 files changed

+589
-240
lines changed

docs/plugins/repository-gcs.asciidoc

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,36 +116,89 @@ PUT _snapshot/my_gcs_repository
116116
// CONSOLE
117117
// TEST[skip:we don't have gcs setup while testing this]
118118

119-
[[repository-gcs-bucket-permission]]
120-
===== Set Bucket Permission
119+
[[repository-gcs-client]]
120+
==== Client Settings
121121

122-
The service account used to access the bucket must have the "Writer" access to the bucket:
122+
The client used to connect to Google Cloud Storage has a number of settings available.
123+
Client setting names are of the form `gcs.client.CLIENT_NAME.SETTING_NAME` and specified
124+
inside `elasticsearch.yml`. The default client name looked up by a `gcs` repository is
125+
called `default`, but can be customized with the repository setting `client`.
123126

124-
1. Connect to the https://console.cloud.google.com/[Google Cloud Platform Console]
125-
2. Select your project
126-
3. Got to the https://console.cloud.google.com/storage/browser[Storage Browser]
127-
4. Select the bucket and "Edit bucket permission"
128-
5. The service account must be configured as a "User" with "Writer" access
127+
For example:
128+
129+
[source,js]
130+
----
131+
PUT _snapshot/my_gcs_repository
132+
{
133+
"type": "gcs",
134+
"settings": {
135+
"bucket": "my_bucket",
136+
"client": "my_alternate_client"
137+
}
138+
}
139+
----
140+
// CONSOLE
141+
// TEST[skip:we don't have gcs setup while testing this]
142+
143+
Some settings are sensitive and must be stored in the
144+
{ref}/secure-settings.html[elasticsearch keystore]. This is the case for the service account file:
145+
146+
[source,sh]
147+
----
148+
bin/elasticsearch-keystore add-file gcs.client.default.credentials_file
149+
----
150+
151+
The following are the available client settings. Those that must be stored in the keystore
152+
are marked as `Secure`.
129153

154+
`credentials_file`::
155+
156+
The service account file that is used to authenticate to the Google Cloud Storage service. (Secure)
157+
158+
`endpoint`::
159+
160+
The Google Cloud Storage service endpoint to connect to. This will be automatically
161+
determined by the Google Cloud Storage client but can be specified explicitly.
162+
163+
`connect_timeout`::
164+
165+
The timeout to establish a connection to the Google Cloud Storage service. The value should
166+
specify the unit. For example, a value of `5s` specifies a 5 second timeout. The value of `-1`
167+
corresponds to an infinite timeout. The default value is 20 seconds.
168+
169+
`read_timeout`::
170+
171+
The timeout to read data from an established connection. The value should
172+
specify the unit. For example, a value of `5s` specifies a 5 second timeout. The value of `-1`
173+
corresponds to an infinite timeout. The default value is 20 seconds.
174+
175+
`application_name`::
176+
177+
Name used by the client when it uses the Google Cloud Storage service. Setting
178+
a custom name can be useful to authenticate your cluster when requests
179+
statistics are logged in the Google Cloud Platform. Default to `repository-gcs`
130180

131181
[[repository-gcs-repository]]
132-
==== Create a Repository
182+
==== Repository Settings
183+
184+
The `gcs` repository type supports a number of settings to customize how data
185+
is stored in Google Cloud Storage.
133186

134-
Once everything is installed and every node is started, you can create a new repository that
135-
uses Google Cloud Storage to store snapshots:
187+
These can be specified when creating the repository. For example:
136188

137189
[source,js]
138190
----
139191
PUT _snapshot/my_gcs_repository
140192
{
141193
"type": "gcs",
142194
"settings": {
143-
"bucket": "my_bucket"
195+
"bucket": "my_other_bucket",
196+
"base_path": "dev"
144197
}
145198
}
146199
----
147200
// CONSOLE
148-
// TEST[skip:we don't have gcs setup while testing this]
201+
// TEST[skip:we don't have gcs set up while testing this]
149202

150203
The following settings are supported:
151204

@@ -155,8 +208,8 @@ The following settings are supported:
155208

156209
`client`::
157210

158-
The client congfiguration to use. This controls which credentials are used to connect
159-
to Compute Engine.
211+
The name of the client to use to connect to Google Cloud Storage.
212+
Defaults to `default`.
160213

161214
`base_path`::
162215

@@ -177,6 +230,15 @@ The following settings are supported:
177230

178231
`application_name`::
179232

180-
Name used by the plugin when it uses the Google Cloud JSON API. Setting
181-
a custom name can be useful to authenticate your cluster when requests
182-
statistics are logged in the Google Cloud Platform. Default to `repository-gcs`
233+
deprecated[7.0.0, This setting is now defined in the <<repository-gcs-client, client settings>>]
234+
235+
[[repository-gcs-bucket-permission]]
236+
===== Recommended Bucket Permission
237+
238+
The service account used to access the bucket must have the "Writer" access to the bucket:
239+
240+
1. Connect to the https://console.cloud.google.com/[Google Cloud Platform Console]
241+
2. Select your project
242+
3. Got to the https://console.cloud.google.com/storage/browser[Storage Browser]
243+
4. Select the bucket and "Edit bucket permission"
244+
5. The service account must be configured as a "User" with "Writer" access

docs/plugins/repository-s3.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ PUT _snapshot/my_s3_repository
3636

3737
The client used to connect to S3 has a number of settings available. Client setting names are of
3838
the form `s3.client.CLIENT_NAME.SETTING_NAME` and specified inside `elasticsearch.yml`. The
39-
default client name looked up by an s3 repository is called `default`, but can be customized
39+
default client name looked up by a `s3` repository is called `default`, but can be customized
4040
with the repository setting `client`. For example:
4141

4242
[source,js]

docs/reference/migration/migrate_7_0/plugins.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ You must set it per azure client instead. Like `azure.client.default.timeout: 10
1212

1313
See {plugins}/repository-azure-usage.html#repository-azure-repository-settings[Azure Repository settings].
1414

15+
==== Google Cloud Storage Repository plugin
16+
17+
* The repository settings `application_name`, `connect_timeout` and `read_timeout` have been removed and
18+
must now be specified in the client settings instead.
19+
20+
See {plugins}/repository-gcs-client.html#repository-gcs-client[Google Cloud Storage Client Settings].
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.repositories.gcs;
20+
21+
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
22+
import com.google.api.services.storage.StorageScopes;
23+
import org.elasticsearch.common.settings.SecureSetting;
24+
import org.elasticsearch.common.settings.Setting;
25+
import org.elasticsearch.common.settings.Settings;
26+
import org.elasticsearch.common.unit.TimeValue;
27+
28+
import java.io.IOException;
29+
import java.io.InputStream;
30+
import java.io.UncheckedIOException;
31+
import java.util.Collections;
32+
import java.util.HashMap;
33+
import java.util.Locale;
34+
import java.util.Map;
35+
36+
import static org.elasticsearch.common.settings.Setting.timeSetting;
37+
38+
/**
39+
* Container for Google Cloud Storage clients settings.
40+
*/
41+
public class GoogleCloudStorageClientSettings {
42+
43+
private static final String PREFIX = "gcs.client.";
44+
45+
/** A json Service Account file loaded from secure settings. */
46+
static final Setting.AffixSetting<InputStream> CREDENTIALS_FILE_SETTING = Setting.affixKeySetting(PREFIX, "credentials_file",
47+
key -> SecureSetting.secureFile(key, null));
48+
49+
/** An override for the Storage endpoint to connect to. */
50+
static final Setting.AffixSetting<String> ENDPOINT_SETTING = Setting.affixKeySetting(PREFIX, "endpoint",
51+
key -> new Setting<>(key, "", s -> s, Setting.Property.NodeScope));
52+
53+
/**
54+
* The timeout to establish a connection. A value of {@code -1} corresponds to an infinite timeout. A value of {@code 0}
55+
* corresponds to the default timeout of the Google Cloud Storage Java Library.
56+
*/
57+
static final Setting.AffixSetting<TimeValue> CONNECT_TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "connect_timeout",
58+
key -> timeSetting(key, TimeValue.ZERO, TimeValue.MINUS_ONE, Setting.Property.NodeScope));
59+
60+
/**
61+
* The timeout to read data from an established connection. A value of {@code -1} corresponds to an infinite timeout. A value of
62+
* {@code 0} corresponds to the default timeout of the Google Cloud Storage Java Library.
63+
*/
64+
static final Setting.AffixSetting<TimeValue> READ_TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "read_timeout",
65+
key -> timeSetting(key, TimeValue.ZERO, TimeValue.MINUS_ONE, Setting.Property.NodeScope));
66+
67+
/** Name used by the client when it uses the Google Cloud JSON API. **/
68+
static final Setting.AffixSetting<String> APPLICATION_NAME_SETTING = Setting.affixKeySetting(PREFIX, "application_name",
69+
key -> new Setting<>(key, "repository-gcs", s -> s, Setting.Property.NodeScope));
70+
71+
/** The credentials used by the client to connect to the Storage endpoint **/
72+
private final GoogleCredential credential;
73+
74+
/** The Storage root URL the client should talk to, or empty string to use the default. **/
75+
private final String endpoint;
76+
77+
/** The timeout to establish a connection **/
78+
private final TimeValue connectTimeout;
79+
80+
/** The timeout to read data from an established connection **/
81+
private final TimeValue readTimeout;
82+
83+
/** The Storage client application name **/
84+
private final String applicationName;
85+
86+
GoogleCloudStorageClientSettings(final GoogleCredential credential,
87+
final String endpoint,
88+
final TimeValue connectTimeout,
89+
final TimeValue readTimeout,
90+
final String applicationName) {
91+
this.credential = credential;
92+
this.endpoint = endpoint;
93+
this.connectTimeout = connectTimeout;
94+
this.readTimeout = readTimeout;
95+
this.applicationName = applicationName;
96+
}
97+
98+
public GoogleCredential getCredential() {
99+
return credential;
100+
}
101+
102+
public String getEndpoint() {
103+
return endpoint;
104+
}
105+
106+
public TimeValue getConnectTimeout() {
107+
return connectTimeout;
108+
}
109+
110+
public TimeValue getReadTimeout() {
111+
return readTimeout;
112+
}
113+
114+
public String getApplicationName() {
115+
return applicationName;
116+
}
117+
118+
public static Map<String, GoogleCloudStorageClientSettings> load(final Settings settings) {
119+
final Map<String, GoogleCloudStorageClientSettings> clients = new HashMap<>();
120+
for (String clientName: settings.getGroups(PREFIX).keySet()) {
121+
clients.put(clientName, getClientSettings(settings, clientName));
122+
}
123+
if (clients.containsKey("default") == false) {
124+
// this won't find any settings under the default client,
125+
// but it will pull all the fallback static settings
126+
clients.put("default", getClientSettings(settings, "default"));
127+
}
128+
return Collections.unmodifiableMap(clients);
129+
}
130+
131+
static GoogleCloudStorageClientSettings getClientSettings(final Settings settings, final String clientName) {
132+
return new GoogleCloudStorageClientSettings(
133+
loadCredential(settings, clientName),
134+
getConfigValue(settings, clientName, ENDPOINT_SETTING),
135+
getConfigValue(settings, clientName, CONNECT_TIMEOUT_SETTING),
136+
getConfigValue(settings, clientName, READ_TIMEOUT_SETTING),
137+
getConfigValue(settings, clientName, APPLICATION_NAME_SETTING)
138+
);
139+
}
140+
141+
/**
142+
* Loads the service account file corresponding to a given client name. If no file is defined for the client,
143+
* a {@code null} credential is returned.
144+
*
145+
* @param settings the {@link Settings}
146+
* @param clientName the client name
147+
*
148+
* @return the {@link GoogleCredential} to use for the given client, {@code null} if no service account is defined.
149+
*/
150+
static GoogleCredential loadCredential(final Settings settings, final String clientName) {
151+
try {
152+
if (CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).exists(settings) == false) {
153+
// explicitly returning null here so that the default credential
154+
// can be loaded later when creating the Storage client
155+
return null;
156+
}
157+
try (InputStream credStream = CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).get(settings)) {
158+
GoogleCredential credential = GoogleCredential.fromStream(credStream);
159+
if (credential.createScopedRequired()) {
160+
credential = credential.createScoped(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL));
161+
}
162+
return credential;
163+
}
164+
} catch (IOException e) {
165+
throw new UncheckedIOException(e);
166+
}
167+
}
168+
169+
private static <T> T getConfigValue(final Settings settings, final String clientName, final Setting.AffixSetting<T> clientSetting) {
170+
Setting<T> concreteSetting = clientSetting.getConcreteSettingForNamespace(clientName);
171+
return concreteSetting.get(settings);
172+
}
173+
}

plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStoragePlugin.java

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,8 @@
1919

2020
package org.elasticsearch.repositories.gcs;
2121

22-
import java.security.AccessController;
23-
import java.security.PrivilegedAction;
24-
import java.util.Collections;
25-
import java.util.List;
26-
import java.util.Map;
27-
2822
import com.google.api.client.auth.oauth2.TokenRequest;
2923
import com.google.api.client.auth.oauth2.TokenResponse;
30-
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
3124
import com.google.api.client.googleapis.json.GoogleJsonError;
3225
import com.google.api.client.http.GenericUrl;
3326
import com.google.api.client.http.HttpHeaders;
@@ -48,12 +41,15 @@
4841
import org.elasticsearch.plugins.Plugin;
4942
import org.elasticsearch.plugins.RepositoryPlugin;
5043
import org.elasticsearch.repositories.Repository;
51-
import org.elasticsearch.repositories.gcs.GoogleCloudStorageRepository;
52-
import org.elasticsearch.repositories.gcs.GoogleCloudStorageService;
5344

54-
public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin {
45+
import java.security.AccessController;
46+
import java.security.PrivilegedAction;
47+
import java.util.Arrays;
48+
import java.util.Collections;
49+
import java.util.List;
50+
import java.util.Map;
5551

56-
public static final String NAME = "repository-gcs";
52+
public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin {
5753

5854
static {
5955
/*
@@ -112,15 +108,19 @@ public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin
112108
});
113109
}
114110

115-
private final Map<String, GoogleCredential> credentials;
111+
private final Map<String, GoogleCloudStorageClientSettings> clientsSettings;
112+
113+
public GoogleCloudStoragePlugin(final Settings settings) {
114+
clientsSettings = GoogleCloudStorageClientSettings.load(settings);
115+
}
116116

117-
public GoogleCloudStoragePlugin(Settings settings) {
118-
credentials = GoogleCloudStorageService.loadClientCredentials(settings);
117+
protected Map<String, GoogleCloudStorageClientSettings> getClientsSettings() {
118+
return clientsSettings;
119119
}
120120

121121
// overridable for tests
122122
protected GoogleCloudStorageService createStorageService(Environment environment) {
123-
return new GoogleCloudStorageService.InternalGoogleCloudStorageService(environment, credentials);
123+
return new GoogleCloudStorageService(environment, clientsSettings);
124124
}
125125

126126
@Override
@@ -131,6 +131,11 @@ public Map<String, Repository.Factory> getRepositories(Environment env, NamedXCo
131131

132132
@Override
133133
public List<Setting<?>> getSettings() {
134-
return Collections.singletonList(GoogleCloudStorageService.CREDENTIALS_FILE_SETTING);
134+
return Arrays.asList(
135+
GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING,
136+
GoogleCloudStorageClientSettings.ENDPOINT_SETTING,
137+
GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING,
138+
GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING,
139+
GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING);
135140
}
136141
}

0 commit comments

Comments
 (0)