Skip to content

Commit ca61a21

Browse files
jaschrep-msftJoshLove-msftjsquire
authored
GeoRedundantReadMode (Azure#30368)
* infra for georedundancy mode * tests and blobclientoptions * clientoptions successfully build policy * datalake options * queue redundancy * exportapi * Update sdk/core/Azure.Core/src/Shared/Argument.cs Co-authored-by: JoshLove-msft <[email protected]> * Update sdk/core/Azure.Core/src/Shared/Argument.cs Co-authored-by: Jesse Squire <[email protected]> * Argument.AssertEnumDefined docstrings * removed redundant options * exportapi * changelog Co-authored-by: JoshLove-msft <[email protected]> Co-authored-by: Jesse Squire <[email protected]>
1 parent b8af831 commit ca61a21

21 files changed

+320
-34
lines changed

sdk/core/Azure.Core/src/Shared/Argument.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,21 @@ public static void AssertInRange<T>(T value, T minimum, T maximum, string name)
180180
}
181181
}
182182

183+
/// <summary>
184+
/// Throws if <paramref name="value"/> is not defined for <paramref name="enumType"/>.
185+
/// </summary>
186+
/// <param name="enumType">The type to validate against.</param>
187+
/// <param name="value">The value to validate.</param>
188+
/// <param name="name">The name of the parameter.</param>
189+
/// <exception cref="ArgumentException"><paramref name="value"/> is not defined for <paramref name="enumType"/>.</exception>
190+
public static void AssertEnumDefined(Type enumType, object value, string name)
191+
{
192+
if (!Enum.IsDefined(enumType, value))
193+
{
194+
throw new ArgumentException($"Value not defined for {enumType.FullName}.", name);
195+
}
196+
}
197+
183198
/// <summary>
184199
/// Throws if <paramref name="value"/> has not been initialized; otherwise, returns <paramref name="value"/>.
185200
/// </summary>

sdk/storage/Azure.Storage.Blobs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 12.14.0-beta.2 (Unreleased)
44

55
### Features Added
6+
- Added support for "secondary then primary" read operations when using geo-redundant accounts.
67
- Added support for leading and trailing '/' characters in blob names when constructing URIs via builder.
78

89
### Breaking Changes

sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public BlobClientOptions(Azure.Storage.Blobs.BlobClientOptions.ServiceVersion ve
5555
public Azure.Storage.Blobs.Models.CustomerProvidedKey? CustomerProvidedKey { get { throw null; } set { } }
5656
public bool EnableTenantDiscovery { get { throw null; } set { } }
5757
public string EncryptionScope { get { throw null; } set { } }
58+
public Azure.Storage.GeoRedundantReadMode GeoRedundantReadMode { get { throw null; } set { } }
5859
public System.Uri GeoRedundantSecondaryUri { get { throw null; } set { } }
5960
public bool PreserveBlobNameSlashes { get { throw null; } set { } }
6061
public Azure.Storage.TransferValidationOptions TransferValidation { get { throw null; } }

sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.1.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public BlobClientOptions(Azure.Storage.Blobs.BlobClientOptions.ServiceVersion ve
5555
public Azure.Storage.Blobs.Models.CustomerProvidedKey? CustomerProvidedKey { get { throw null; } set { } }
5656
public bool EnableTenantDiscovery { get { throw null; } set { } }
5757
public string EncryptionScope { get { throw null; } set { } }
58+
public Azure.Storage.GeoRedundantReadMode GeoRedundantReadMode { get { throw null; } set { } }
5859
public System.Uri GeoRedundantSecondaryUri { get { throw null; } set { } }
5960
public bool PreserveBlobNameSlashes { get { throw null; } set { } }
6061
public Azure.Storage.TransferValidationOptions TransferValidation { get { throw null; } }

sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ public enum ServiceVersion
135135
/// </summary>
136136
public Uri GeoRedundantSecondaryUri { get; set; }
137137

138+
/// <summary>
139+
/// Strategy to take when sending requests and retries between primary and secondary endpoints.
140+
/// Ignored when <see cref="GeoRedundantSecondaryUri"/> is not set.
141+
/// Defaults to <see cref="GeoRedundantReadMode.PrimaryThenSecondary"/>.
142+
/// </summary>
143+
public GeoRedundantReadMode GeoRedundantReadMode { get; set; }
144+
138145
/// <summary>
139146
/// Configures whether to send or receive checksum headers for blob uploads and downloads. Downloads
140147
/// can optionally validate that the content matches the checksum.
@@ -299,7 +306,7 @@ private void AddHeadersAndQueryParameters()
299306
/// <returns>An HttpPipeline to use for Storage requests.</returns>
300307
internal HttpPipeline Build(HttpPipelinePolicy authentication = null)
301308
{
302-
return this.Build(authentication, GeoRedundantSecondaryUri);
309+
return this.Build(authentication, GeoRedundantSecondaryUri, GeoRedundantReadMode);
303310
}
304311

305312
/// <summary>
@@ -309,7 +316,7 @@ internal HttpPipeline Build(HttpPipelinePolicy authentication = null)
309316
/// <returns>An HttpPipeline to use for Storage requests.</returns>
310317
internal HttpPipeline Build(object credentials)
311318
{
312-
return this.Build(credentials, GeoRedundantSecondaryUri);
319+
return this.Build(credentials, GeoRedundantSecondaryUri, GeoRedundantReadMode);
313320
}
314321

315322
/// <inheritdoc />
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Threading;
6+
using Azure.Core;
7+
using Azure.Core.TestFramework;
8+
using Azure.Storage.Test.Shared;
9+
using NUnit.Framework;
10+
11+
namespace Azure.Storage.Blobs.Tests
12+
{
13+
public class BlobClientOptionsTests
14+
{
15+
[Test]
16+
public void SetGeoRedundancyMode()
17+
{
18+
const string primaryHost = "foo.blob.core.windows.net";
19+
var primaryUri = new Uri("https://" + primaryHost);
20+
const string secondaryHost = "foo-secondary.blob.core.windows.net";
21+
var secondaryUri = new Uri("https://" + secondaryHost);
22+
23+
// set mode that changes first request
24+
var options = new BlobClientOptions
25+
{
26+
GeoRedundantSecondaryUri = secondaryUri,
27+
GeoRedundantReadMode = GeoRedundantReadMode.SecondaryThenPrimary,
28+
Transport = new MockTransport(new MockResponse(200))
29+
};
30+
// policy to observe change in pipeline (observe changed to secondary host)
31+
options.AddPolicy(
32+
new AssertMessageContentsPolicy(checkRequest: r => Assert.AreEqual(secondaryHost, r.Uri.Host))
33+
{
34+
CheckRequest = true
35+
},
36+
HttpPipelinePosition.PerRetry);
37+
38+
var request = new MockRequest()
39+
{
40+
Method = RequestMethod.Get,
41+
};
42+
// original request to primary host
43+
request.Uri.Reset(primaryUri);
44+
45+
// Act
46+
options.Build().SendRequest(request, CancellationToken.None);
47+
48+
// Assertion in pipeline
49+
}
50+
}
51+
}

sdk/storage/Azure.Storage.Common/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 12.13.0-beta.2 (Unreleased)
44

55
### Features Added
6+
- Added support for "secondary then primary" read operations when using geo-redundant accounts.
67

78
### Breaking Changes
89

sdk/storage/Azure.Storage.Common/api/Azure.Storage.Common.netstandard2.0.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public DownloadTransferValidationOptions() { }
2020
public bool AutoValidateChecksum { get { throw null; } set { } }
2121
public Azure.Storage.StorageChecksumAlgorithm ChecksumAlgorithm { get { throw null; } set { } }
2222
}
23+
public enum GeoRedundantReadMode
24+
{
25+
PrimaryThenSecondary = 0,
26+
SecondaryThenPrimary = 1,
27+
}
2328
public enum StorageChecksumAlgorithm
2429
{
2530
Auto = 0,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.Storage
5+
{
6+
/// <summary>
7+
/// Pattern for retrying a read request between geo-redundant primary and secondary endpoints.
8+
/// </summary>
9+
public enum GeoRedundantReadMode
10+
{
11+
/// <summary>
12+
/// Altnernate between primary and secondary endpoints, attempting primary first.
13+
/// </summary>
14+
PrimaryThenSecondary = 0,
15+
16+
/// <summary>
17+
/// Altnernate between primary and secondary endpoints, attempting secondary first.
18+
/// </summary>
19+
SecondaryThenPrimary = 1
20+
}
21+
}

sdk/storage/Azure.Storage.Common/src/Shared/GeoRedundantReadPolicy.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Net.Mail;
56
using Azure.Core;
67
using Azure.Core.Pipeline;
78

@@ -15,13 +16,17 @@ internal class GeoRedundantReadPolicy : HttpPipelineSynchronousPolicy
1516
{
1617
private readonly string _secondaryStorageHost;
1718

18-
public GeoRedundantReadPolicy(Uri secondaryStorageUri)
19+
private readonly GeoRedundantReadMode _mode;
20+
21+
public GeoRedundantReadPolicy(Uri secondaryStorageUri, GeoRedundantReadMode mode)
1922
{
2023
if (secondaryStorageUri == null)
2124
{
2225
throw Errors.ArgumentNull(nameof(secondaryStorageUri));
2326
}
27+
Argument.AssertEnumDefined(typeof(GeoRedundantReadMode), mode, nameof(mode));
2428
_secondaryStorageHost = secondaryStorageUri.Host;
29+
_mode = mode;
2530
}
2631

2732
public override void OnSendingRequest(HttpMessage message)
@@ -39,10 +44,28 @@ public override void OnSendingRequest(HttpMessage message)
3944
out var alternateHostObj)
4045
? alternateHostObj as string
4146
: null;
47+
// first message
4248
if (alternateHost == null)
4349
{
44-
// queue up the secondary host for subsequent retries
45-
message.SetProperty(Constants.GeoRedundantRead.AlternateHostKey, _secondaryStorageHost);
50+
string primaryHost = message.Request.Uri.Host;
51+
52+
// set appropriate first host
53+
message.Request.Uri.Host = _mode switch
54+
{
55+
GeoRedundantReadMode.PrimaryThenSecondary => primaryHost,
56+
GeoRedundantReadMode.SecondaryThenPrimary => _secondaryStorageHost,
57+
_ => throw BadModeError()
58+
};
59+
60+
// queue up the appropriate host for subsequent retry
61+
message.SetProperty(
62+
Constants.GeoRedundantRead.AlternateHostKey,
63+
_mode switch
64+
{
65+
GeoRedundantReadMode.PrimaryThenSecondary => _secondaryStorageHost,
66+
GeoRedundantReadMode.SecondaryThenPrimary => primaryHost,
67+
_ => throw BadModeError()
68+
});
4669
return;
4770
}
4871

@@ -75,5 +98,8 @@ public override void OnSendingRequest(HttpMessage message)
7598
message.Request.Uri.Host = alternateHost;
7699
message.SetProperty(Constants.GeoRedundantRead.AlternateHostKey, lastTriedHost);
77100
}
101+
102+
private InvalidOperationException BadModeError() => new(
103+
$"Unexpected mode {Enum.GetName(typeof(GeoRedundantReadMode), _mode)} when alternating between primary and secondary endpoints.");
78104
}
79105
}

0 commit comments

Comments
 (0)