Browse Source

Add support for custom endpoints in the Azure repository (#94576)

Closes #94537
Francisco Fernández Castaño 2 years ago
parent
commit
da9bb382f5

+ 6 - 0
docs/changelog/94576.yaml

@@ -0,0 +1,6 @@
+pr: 94576
+summary: Add support for custom endpoints in the Azure repository
+area: Snapshot/Restore
+type: enhancement
+issues:
+ - 94537

+ 6 - 0
docs/reference/snapshot-restore/repository-azure.asciidoc

@@ -140,6 +140,12 @@ stored in the keystore are marked as "secure"; the other settings belong in the
   set by the Azure client (known as 5 minutes). This setting can be defined
   globally, per account, or both.
 
+`endpoint`::
+  The Azure endpoint to connect to. It must include the protocol used to connect to Azure.
+
+`secondary_endpoint`::
+  The Azure secondary endpoint to connect to. It must include the protocol used to connect to Azure.
+
 [[repository-azure-repository-settings]]
 ==== Repository settings
 

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

@@ -176,7 +176,7 @@ class AzureClientProvider extends AbstractLifecycleComponent {
             String secondaryUri = settings.getStorageEndpoint().secondaryURI();
             if (secondaryUri == null) {
                 throw new IllegalArgumentException(
-                    "Unable to configure an AzureClient using a secondary location without a secondary " + "endpoint"
+                    "Unable to configure an AzureClient using a secondary location without a secondary endpoint"
                 );
             }
 

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

@@ -118,7 +118,9 @@ public class AzureRepositoryPlugin extends Plugin implements RepositoryPlugin, R
             AzureStorageSettings.MAX_RETRIES_SETTING,
             AzureStorageSettings.PROXY_TYPE_SETTING,
             AzureStorageSettings.PROXY_HOST_SETTING,
-            AzureStorageSettings.PROXY_PORT_SETTING
+            AzureStorageSettings.PROXY_PORT_SETTING,
+            AzureStorageSettings.ENDPOINT_SETTING,
+            AzureStorageSettings.SECONDARY_ENDPOINT_SETTING
         );
     }
 

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

@@ -75,6 +75,20 @@ final class AzureStorageSettings {
         () -> ACCOUNT_SETTING
     );
 
+    public static final AffixSetting<String> ENDPOINT_SETTING = Setting.affixKeySetting(
+        AZURE_CLIENT_PREFIX_KEY,
+        "endpoint",
+        key -> Setting.simpleString(key, Property.NodeScope),
+        () -> ACCOUNT_SETTING
+    );
+
+    public static final AffixSetting<String> SECONDARY_ENDPOINT_SETTING = Setting.affixKeySetting(
+        AZURE_CLIENT_PREFIX_KEY,
+        "secondary_endpoint",
+        key -> Setting.simpleString(key, Property.NodeScope),
+        () -> ACCOUNT_SETTING
+    );
+
     public static final AffixSetting<TimeValue> TIMEOUT_SETTING = Setting.affixKeySetting(
         AZURE_CLIENT_PREFIX_KEY,
         "timeout",
@@ -129,11 +143,13 @@ final class AzureStorageSettings {
         int maxRetries,
         Proxy.Type proxyType,
         String proxyHost,
-        Integer proxyPort
+        Integer proxyPort,
+        String endpoint,
+        String secondaryEndpoint
     ) {
         this.account = account;
         this.sasToken = sasToken;
-        this.connectString = buildConnectString(account, key, sasToken, endpointSuffix);
+        this.connectString = buildConnectString(account, key, sasToken, endpointSuffix, endpoint, secondaryEndpoint);
         this.endpointSuffix = endpointSuffix;
         this.timeout = timeout;
         this.maxRetries = maxRetries;
@@ -157,10 +173,6 @@ final class AzureStorageSettings {
         }
     }
 
-    public String getSasToken() {
-        return sasToken;
-    }
-
     public String getEndpointSuffix() {
         return endpointSuffix;
     }
@@ -181,7 +193,14 @@ final class AzureStorageSettings {
         return connectString;
     }
 
-    private static String buildConnectString(String account, @Nullable String key, @Nullable String sasToken, String endpointSuffix) {
+    private static String buildConnectString(
+        String account,
+        @Nullable String key,
+        @Nullable String sasToken,
+        String endpointSuffix,
+        @Nullable String endpoint,
+        @Nullable String secondaryEndpoint
+    ) {
         final boolean hasSasToken = Strings.hasText(sasToken);
         final boolean hasKey = Strings.hasText(key);
         if (hasSasToken == false && hasKey == false) {
@@ -197,9 +216,34 @@ final class AzureStorageSettings {
         } else {
             connectionStringBuilder.append(";SharedAccessSignature=").append(sasToken);
         }
-        if (Strings.hasText(endpointSuffix)) {
+        final boolean hasEndpointSuffix = Strings.hasText(endpointSuffix);
+        final boolean hasEndpoint = Strings.hasText(endpoint);
+        final boolean hasSecondaryEndpoint = Strings.hasText(secondaryEndpoint);
+
+        if (hasEndpointSuffix && hasEndpoint) {
+            throw new SettingsException("Both an endpoint suffix as well as a primary endpoint were set");
+        }
+
+        if (hasEndpointSuffix && hasSecondaryEndpoint) {
+            throw new SettingsException("Both an endpoint suffix as well as a secondary endpoint were set");
+        }
+
+        if (hasEndpoint == false && hasSecondaryEndpoint) {
+            throw new SettingsException("A primary endpoint is required when setting a secondary endpoint");
+        }
+
+        if (hasEndpointSuffix) {
             connectionStringBuilder.append(";EndpointSuffix=").append(endpointSuffix);
         }
+
+        if (hasEndpoint) {
+            connectionStringBuilder.append(";BlobEndpoint=").append(endpoint);
+        }
+
+        if (hasSecondaryEndpoint) {
+            connectionStringBuilder.append(";BlobSecondaryEndpoint=").append(secondaryEndpoint);
+        }
+
         return connectionStringBuilder.toString();
     }
 
@@ -253,7 +297,9 @@ final class AzureStorageSettings {
                 getValue(settings, clientName, MAX_RETRIES_SETTING),
                 getValue(settings, clientName, PROXY_TYPE_SETTING),
                 getValue(settings, clientName, PROXY_HOST_SETTING),
-                getValue(settings, clientName, PROXY_PORT_SETTING)
+                getValue(settings, clientName, PROXY_PORT_SETTING),
+                getValue(settings, clientName, ENDPOINT_SETTING),
+                getValue(settings, clientName, SECONDARY_ENDPOINT_SETTING)
             );
         }
     }

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

@@ -450,6 +450,69 @@ public class AzureStorageServiceTests extends ESTestCase {
         }
     }
 
+    public void testCreateClientWithEndpoints() throws IOException {
+        final Settings settings = Settings.builder()
+            .setSecureSettings(buildSecureSettings())
+            .put("azure.client.azure1.endpoint", "https://account1.zone.azure.net")
+
+            .put("azure.client.azure2.endpoint", "https://account2.zone.azure.net")
+            .put("azure.client.azure2.secondary_endpoint", "https://account2-secondary.zone.azure.net")
+            .build();
+        try (AzureRepositoryPlugin plugin = pluginWithSettingsValidation(settings)) {
+            final AzureStorageService azureStorageService = plugin.azureStoreService.get();
+
+            expectThrows(IllegalArgumentException.class, () -> azureStorageService.client("azure1", LocationMode.PRIMARY_THEN_SECONDARY));
+            expectThrows(IllegalArgumentException.class, () -> azureStorageService.client("azure1", LocationMode.SECONDARY_ONLY));
+            expectThrows(IllegalArgumentException.class, () -> azureStorageService.client("azure1", LocationMode.SECONDARY_THEN_PRIMARY));
+
+            AzureBlobServiceClient client1 = azureStorageService.client("azure1", LocationMode.PRIMARY_ONLY);
+            assertThat(client1.getSyncClient().getAccountUrl(), equalTo("https://account1.zone.azure.net"));
+
+            assertThat(
+                azureStorageService.client("azure2", randomBoolean() ? LocationMode.PRIMARY_ONLY : LocationMode.PRIMARY_THEN_SECONDARY)
+                    .getSyncClient()
+                    .getAccountUrl(),
+                equalTo("https://account2.zone.azure.net")
+            );
+
+            assertThat(
+                azureStorageService.client("azure2", randomBoolean() ? LocationMode.SECONDARY_ONLY : LocationMode.SECONDARY_THEN_PRIMARY)
+                    .getSyncClient()
+                    .getAccountUrl(),
+                equalTo("https://account2-secondary.zone.azure.net")
+            );
+        }
+    }
+
+    public void testEndpointSettingValidation() {
+        {
+            final SettingsException e = expectThrows(
+                SettingsException.class,
+                () -> storageServiceWithSettingsValidation(
+                    Settings.builder()
+                        .setSecureSettings(buildSecureSettings())
+                        .put("azure.client.azure1.secondary_endpoint", "https://account1.zone.azure.net")
+                        .build()
+                )
+            );
+            assertEquals("A primary endpoint is required when setting a secondary endpoint", e.getMessage());
+        }
+
+        {
+            final SettingsException e = expectThrows(
+                SettingsException.class,
+                () -> storageServiceWithSettingsValidation(
+                    Settings.builder()
+                        .setSecureSettings(buildSecureSettings())
+                        .put("azure.client.azure1.endpoint_suffix", "test")
+                        .put("azure.client.azure1.secondary_endpoint", "https://account1.zone.azure.net")
+                        .build()
+                )
+            );
+            assertEquals("Both an endpoint suffix as well as a secondary endpoint were set", e.getMessage());
+        }
+    }
+
     private static MockSecureSettings buildSecureSettings() {
         final MockSecureSettings secureSettings = new MockSecureSettings();
         secureSettings.setString("azure.client.azure1.account", "myaccount1");