Skip to content

Commit da9bb38

Browse files
authored
Add support for custom endpoints in the Azure repository (#94576)
Closes #94537
1 parent 1469ed2 commit da9bb38

File tree

6 files changed

+134
-11
lines changed

6 files changed

+134
-11
lines changed

docs/changelog/94576.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 94576
2+
summary: Add support for custom endpoints in the Azure repository
3+
area: Snapshot/Restore
4+
type: enhancement
5+
issues:
6+
- 94537

docs/reference/snapshot-restore/repository-azure.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ stored in the keystore are marked as "secure"; the other settings belong in the
140140
set by the Azure client (known as 5 minutes). This setting can be defined
141141
globally, per account, or both.
142142

143+
`endpoint`::
144+
The Azure endpoint to connect to. It must include the protocol used to connect to Azure.
145+
146+
`secondary_endpoint`::
147+
The Azure secondary endpoint to connect to. It must include the protocol used to connect to Azure.
148+
143149
[[repository-azure-repository-settings]]
144150
==== Repository settings
145151

modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureClientProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ AzureBlobServiceClient createClient(
176176
String secondaryUri = settings.getStorageEndpoint().secondaryURI();
177177
if (secondaryUri == null) {
178178
throw new IllegalArgumentException(
179-
"Unable to configure an AzureClient using a secondary location without a secondary " + "endpoint"
179+
"Unable to configure an AzureClient using a secondary location without a secondary endpoint"
180180
);
181181
}
182182

modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepositoryPlugin.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ public List<Setting<?>> getSettings() {
118118
AzureStorageSettings.MAX_RETRIES_SETTING,
119119
AzureStorageSettings.PROXY_TYPE_SETTING,
120120
AzureStorageSettings.PROXY_HOST_SETTING,
121-
AzureStorageSettings.PROXY_PORT_SETTING
121+
AzureStorageSettings.PROXY_PORT_SETTING,
122+
AzureStorageSettings.ENDPOINT_SETTING,
123+
AzureStorageSettings.SECONDARY_ENDPOINT_SETTING
122124
);
123125
}
124126

modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageSettings.java

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ final class AzureStorageSettings {
7575
() -> ACCOUNT_SETTING
7676
);
7777

78+
public static final AffixSetting<String> ENDPOINT_SETTING = Setting.affixKeySetting(
79+
AZURE_CLIENT_PREFIX_KEY,
80+
"endpoint",
81+
key -> Setting.simpleString(key, Property.NodeScope),
82+
() -> ACCOUNT_SETTING
83+
);
84+
85+
public static final AffixSetting<String> SECONDARY_ENDPOINT_SETTING = Setting.affixKeySetting(
86+
AZURE_CLIENT_PREFIX_KEY,
87+
"secondary_endpoint",
88+
key -> Setting.simpleString(key, Property.NodeScope),
89+
() -> ACCOUNT_SETTING
90+
);
91+
7892
public static final AffixSetting<TimeValue> TIMEOUT_SETTING = Setting.affixKeySetting(
7993
AZURE_CLIENT_PREFIX_KEY,
8094
"timeout",
@@ -129,11 +143,13 @@ private AzureStorageSettings(
129143
int maxRetries,
130144
Proxy.Type proxyType,
131145
String proxyHost,
132-
Integer proxyPort
146+
Integer proxyPort,
147+
String endpoint,
148+
String secondaryEndpoint
133149
) {
134150
this.account = account;
135151
this.sasToken = sasToken;
136-
this.connectString = buildConnectString(account, key, sasToken, endpointSuffix);
152+
this.connectString = buildConnectString(account, key, sasToken, endpointSuffix, endpoint, secondaryEndpoint);
137153
this.endpointSuffix = endpointSuffix;
138154
this.timeout = timeout;
139155
this.maxRetries = maxRetries;
@@ -157,10 +173,6 @@ private AzureStorageSettings(
157173
}
158174
}
159175

160-
public String getSasToken() {
161-
return sasToken;
162-
}
163-
164176
public String getEndpointSuffix() {
165177
return endpointSuffix;
166178
}
@@ -181,7 +193,14 @@ public String getConnectString() {
181193
return connectString;
182194
}
183195

184-
private static String buildConnectString(String account, @Nullable String key, @Nullable String sasToken, String endpointSuffix) {
196+
private static String buildConnectString(
197+
String account,
198+
@Nullable String key,
199+
@Nullable String sasToken,
200+
String endpointSuffix,
201+
@Nullable String endpoint,
202+
@Nullable String secondaryEndpoint
203+
) {
185204
final boolean hasSasToken = Strings.hasText(sasToken);
186205
final boolean hasKey = Strings.hasText(key);
187206
if (hasSasToken == false && hasKey == false) {
@@ -197,9 +216,34 @@ private static String buildConnectString(String account, @Nullable String key, @
197216
} else {
198217
connectionStringBuilder.append(";SharedAccessSignature=").append(sasToken);
199218
}
200-
if (Strings.hasText(endpointSuffix)) {
219+
final boolean hasEndpointSuffix = Strings.hasText(endpointSuffix);
220+
final boolean hasEndpoint = Strings.hasText(endpoint);
221+
final boolean hasSecondaryEndpoint = Strings.hasText(secondaryEndpoint);
222+
223+
if (hasEndpointSuffix && hasEndpoint) {
224+
throw new SettingsException("Both an endpoint suffix as well as a primary endpoint were set");
225+
}
226+
227+
if (hasEndpointSuffix && hasSecondaryEndpoint) {
228+
throw new SettingsException("Both an endpoint suffix as well as a secondary endpoint were set");
229+
}
230+
231+
if (hasEndpoint == false && hasSecondaryEndpoint) {
232+
throw new SettingsException("A primary endpoint is required when setting a secondary endpoint");
233+
}
234+
235+
if (hasEndpointSuffix) {
201236
connectionStringBuilder.append(";EndpointSuffix=").append(endpointSuffix);
202237
}
238+
239+
if (hasEndpoint) {
240+
connectionStringBuilder.append(";BlobEndpoint=").append(endpoint);
241+
}
242+
243+
if (hasSecondaryEndpoint) {
244+
connectionStringBuilder.append(";BlobSecondaryEndpoint=").append(secondaryEndpoint);
245+
}
246+
203247
return connectionStringBuilder.toString();
204248
}
205249

@@ -253,7 +297,9 @@ private static AzureStorageSettings getClientSettings(Settings settings, String
253297
getValue(settings, clientName, MAX_RETRIES_SETTING),
254298
getValue(settings, clientName, PROXY_TYPE_SETTING),
255299
getValue(settings, clientName, PROXY_HOST_SETTING),
256-
getValue(settings, clientName, PROXY_PORT_SETTING)
300+
getValue(settings, clientName, PROXY_PORT_SETTING),
301+
getValue(settings, clientName, ENDPOINT_SETTING),
302+
getValue(settings, clientName, SECONDARY_ENDPOINT_SETTING)
257303
);
258304
}
259305
}

modules/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceTests.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,69 @@ public void testInvalidSettingsRetryConfigurationForLocationModeWithSecondaryFal
450450
}
451451
}
452452

453+
public void testCreateClientWithEndpoints() throws IOException {
454+
final Settings settings = Settings.builder()
455+
.setSecureSettings(buildSecureSettings())
456+
.put("azure.client.azure1.endpoint", "https://account1.zone.azure.net")
457+
458+
.put("azure.client.azure2.endpoint", "https://account2.zone.azure.net")
459+
.put("azure.client.azure2.secondary_endpoint", "https://account2-secondary.zone.azure.net")
460+
.build();
461+
try (AzureRepositoryPlugin plugin = pluginWithSettingsValidation(settings)) {
462+
final AzureStorageService azureStorageService = plugin.azureStoreService.get();
463+
464+
expectThrows(IllegalArgumentException.class, () -> azureStorageService.client("azure1", LocationMode.PRIMARY_THEN_SECONDARY));
465+
expectThrows(IllegalArgumentException.class, () -> azureStorageService.client("azure1", LocationMode.SECONDARY_ONLY));
466+
expectThrows(IllegalArgumentException.class, () -> azureStorageService.client("azure1", LocationMode.SECONDARY_THEN_PRIMARY));
467+
468+
AzureBlobServiceClient client1 = azureStorageService.client("azure1", LocationMode.PRIMARY_ONLY);
469+
assertThat(client1.getSyncClient().getAccountUrl(), equalTo("https://account1.zone.azure.net"));
470+
471+
assertThat(
472+
azureStorageService.client("azure2", randomBoolean() ? LocationMode.PRIMARY_ONLY : LocationMode.PRIMARY_THEN_SECONDARY)
473+
.getSyncClient()
474+
.getAccountUrl(),
475+
equalTo("https://account2.zone.azure.net")
476+
);
477+
478+
assertThat(
479+
azureStorageService.client("azure2", randomBoolean() ? LocationMode.SECONDARY_ONLY : LocationMode.SECONDARY_THEN_PRIMARY)
480+
.getSyncClient()
481+
.getAccountUrl(),
482+
equalTo("https://account2-secondary.zone.azure.net")
483+
);
484+
}
485+
}
486+
487+
public void testEndpointSettingValidation() {
488+
{
489+
final SettingsException e = expectThrows(
490+
SettingsException.class,
491+
() -> storageServiceWithSettingsValidation(
492+
Settings.builder()
493+
.setSecureSettings(buildSecureSettings())
494+
.put("azure.client.azure1.secondary_endpoint", "https://account1.zone.azure.net")
495+
.build()
496+
)
497+
);
498+
assertEquals("A primary endpoint is required when setting a secondary endpoint", e.getMessage());
499+
}
500+
501+
{
502+
final SettingsException e = expectThrows(
503+
SettingsException.class,
504+
() -> storageServiceWithSettingsValidation(
505+
Settings.builder()
506+
.setSecureSettings(buildSecureSettings())
507+
.put("azure.client.azure1.endpoint_suffix", "test")
508+
.put("azure.client.azure1.secondary_endpoint", "https://account1.zone.azure.net")
509+
.build()
510+
)
511+
);
512+
assertEquals("Both an endpoint suffix as well as a secondary endpoint were set", e.getMessage());
513+
}
514+
}
515+
453516
private static MockSecureSettings buildSecureSettings() {
454517
final MockSecureSettings secureSettings = new MockSecureSettings();
455518
secureSettings.setString("azure.client.azure1.account", "myaccount1");

0 commit comments

Comments
 (0)