Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
99b43b6
Retrieve thinclient endpoint
aavasthy Mar 25, 2025
5ed5bba
update account properties.
aavasthy Mar 25, 2025
a7501fe
Code clean up.
aavasthy Mar 27, 2025
0576713
Merge branch 'master' into users/aavasthy/4999_retrieve_thinclientend…
aavasthy Mar 27, 2025
457ac0f
Keep default falg value as false for thincleint flag
aavasthy Mar 27, 2025
604ba50
Merge branch 'users/aavasthy/4999_retrieve_thinclientendpoint' of htt…
aavasthy Mar 27, 2025
9109a14
Keep default flag value as false for thinclient.
aavasthy Mar 27, 2025
0b0d364
Update endpoint location.
aavasthy Mar 31, 2025
7972c68
Code changes to populate thin client endpoint through gateway account…
kundadebdatta Mar 31, 2025
51ce3cf
Pull other commit on branch.
aavasthy Apr 1, 2025
5b08509
Update formatting issues.
aavasthy Apr 1, 2025
b686a25
Merge branch 'master' into users/aavasthy/4999_retrieve_thinclientend…
aavasthy Apr 1, 2025
5e56352
Fix and add tests.
aavasthy Apr 1, 2025
d228583
Pull latest master.
aavasthy Apr 1, 2025
01f94e1
Code cleanup.
aavasthy Apr 1, 2025
8b2cb96
Merge branch 'master' into users/aavasthy/4999_retrieve_thinclientend…
aavasthy Apr 2, 2025
edacbfb
Merge branch 'master' into users/aavasthy/4999_retrieve_thinclientend…
kundadebdatta Apr 2, 2025
35d9078
Update thinclientStoreModel params
aavasthy Apr 2, 2025
74496e9
Merge with master.
aavasthy Apr 2, 2025
949ccc9
Merge branch 'master' into users/aavasthy/4999_retrieve_thinclientend…
aavasthy Apr 2, 2025
f060071
Merge branch 'master' into users/aavasthy/4999_retrieve_thinclientend…
kundadebdatta Apr 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions Microsoft.Azure.Cosmos/src/DocumentClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ internal partial class DocumentClient : IDisposable, IAuthorizationTokenProvider
private readonly bool isReplicaAddressValidationEnabled;
private readonly bool enableAsyncCacheExceptionNoSharing;

private readonly bool isThinClientEnabled;

//Fault Injection
private readonly IChaosInterceptorFactory chaosInterceptorFactory;
private readonly IChaosInterceptor chaosInterceptor;
Expand Down Expand Up @@ -168,6 +170,7 @@ internal partial class DocumentClient : IDisposable, IAuthorizationTokenProvider
private IStoreClientFactory storeClientFactory;
internal CosmosHttpClient httpClient { get; private set; }

internal CosmosHttpClient thinClientModeHttpClient { get; private set; }
// Flag that indicates whether store client factory must be disposed whenever client is disposed.
// Setting this flag to false will result in store client factory not being disposed when client is disposed.
// This flag is used to allow shared store client factory survive disposition of a document client while other clients continue using it.
Expand Down Expand Up @@ -248,6 +251,7 @@ public DocumentClient(Uri serviceEndpoint,
cancellationToken: this.cancellationTokenSource.Token,
enableAsyncCacheExceptionNoSharing: this.enableAsyncCacheExceptionNoSharing);
this.isReplicaAddressValidationEnabled = ConfigurationManager.IsReplicaAddressValidationEnabled(connectionPolicy);
this.isThinClientEnabled = ConfigurationManager.IsThinClientEnabled(defaultValue: false);
}

/// <summary>
Expand Down Expand Up @@ -505,6 +509,7 @@ internal DocumentClient(Uri serviceEndpoint,
enableAsyncCacheExceptionNoSharing: this.enableAsyncCacheExceptionNoSharing);
this.chaosInterceptorFactory = chaosInterceptorFactory;
this.chaosInterceptor = chaosInterceptorFactory?.CreateInterceptor(this);
this.isThinClientEnabled = ConfigurationManager.IsThinClientEnabled(defaultValue: false);

this.Initialize(
serviceEndpoint: serviceEndpoint,
Expand All @@ -516,7 +521,8 @@ internal DocumentClient(Uri serviceEndpoint,
storeClientFactory: storeClientFactory,
cosmosClientId: cosmosClientId,
remoteCertificateValidationCallback: remoteCertificateValidationCallback,
cosmosClientTelemetryOptions: cosmosClientTelemetryOptions);
cosmosClientTelemetryOptions: cosmosClientTelemetryOptions,
enableThinClientMode: this.isThinClientEnabled);
}

/// <summary>
Expand Down Expand Up @@ -701,7 +707,8 @@ internal virtual void Initialize(Uri serviceEndpoint,
TokenCredential tokenCredential = null,
string cosmosClientId = null,
RemoteCertificateValidationCallback remoteCertificateValidationCallback = null,
CosmosClientTelemetryOptions cosmosClientTelemetryOptions = null)
CosmosClientTelemetryOptions cosmosClientTelemetryOptions = null,
bool enableThinClientMode = false)
{
if (serviceEndpoint == null)
{
Expand Down Expand Up @@ -967,6 +974,17 @@ internal virtual void Initialize(Uri serviceEndpoint,
this.receivedResponse,
this.chaosInterceptor);

if (enableThinClientMode)
Comment thread
aavasthy marked this conversation as resolved.
{
this.thinClientModeHttpClient = CosmosHttpClientCore.CreateWithConnectionPolicy(
this.ApiType,
DocumentClientEventSource.Instance,
this.ConnectionPolicy,
Comment thread
kundadebdatta marked this conversation as resolved.
null,
this.sendingRequest,
this.receivedResponse);
}

// Loading VM Information (non blocking call and initialization won't fail if this call fails)
VmMetadataApiHandler.TryInitialize(this.httpClient);

Expand Down Expand Up @@ -1086,6 +1104,21 @@ private async Task<bool> GetInitializationTaskAsync(IStoreClientFactory storeCli
{
this.StoreModel = this.GatewayStoreModel;
}
else if (this.isThinClientEnabled)
Comment thread
kundadebdatta marked this conversation as resolved.
{
ThinClientStoreModel thinClientStoreModel = new (
endpointManager: this.GlobalEndpointManager,
this.PartitionKeyRangeLocation,
this.sessionContainer,
(Cosmos.ConsistencyLevel)this.accountServiceConfiguration.DefaultConsistencyLevel,
this.eventSource,
this.serializerSettings,
this.thinClientModeHttpClient);

thinClientStoreModel.SetCaches(this.partitionKeyRangeCache, this.collectionCache);

this.StoreModel = thinClientStoreModel;
}
else
{
this.InitializeDirectConnectivity(storeClientFactory);
Expand Down Expand Up @@ -6567,6 +6600,13 @@ internal IStoreModel GetStoreProxy(DocumentServiceRequest request)
return this.GatewayStoreModel;
}

if (this.isThinClientEnabled
Comment thread
aavasthy marked this conversation as resolved.
&& operationType == OperationType.Read
&& resourceType == ResourceType.Database)
{
return this.GatewayStoreModel;
}

if (operationType == OperationType.Create
|| operationType == OperationType.Upsert)
{
Expand Down Expand Up @@ -6786,7 +6826,8 @@ private async Task InitializeGatewayConfigurationReaderAsync()
cosmosAuthorization: this.cosmosAuthorization,
connectionPolicy: this.ConnectionPolicy,
httpClient: this.httpClient,
cancellationToken: this.cancellationTokenSource.Token);
cancellationToken: this.cancellationTokenSource.Token,
isThinClientEnabled: this.isThinClientEnabled);

this.accountServiceConfiguration = new CosmosAccountServiceConfiguration(accountReader.InitializeReaderAsync);

Expand Down
12 changes: 11 additions & 1 deletion Microsoft.Azure.Cosmos/src/GatewayAccountReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Microsoft.Azure.Cosmos

internal sealed class GatewayAccountReader
{
private readonly bool isThinClientEnabled;
private readonly ConnectionPolicy connectionPolicy;
private readonly AuthorizationTokenProvider cosmosAuthorization;
private readonly CosmosHttpClient httpClient;
Expand All @@ -28,13 +29,15 @@ public GatewayAccountReader(Uri serviceEndpoint,
AuthorizationTokenProvider cosmosAuthorization,
ConnectionPolicy connectionPolicy,
CosmosHttpClient httpClient,
CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default,
bool isThinClientEnabled = false)
{
this.httpClient = httpClient;
this.serviceEndpoint = serviceEndpoint;
this.cosmosAuthorization = cosmosAuthorization ?? throw new ArgumentNullException(nameof(AuthorizationTokenProvider));
this.connectionPolicy = connectionPolicy;
this.cancellationToken = cancellationToken;
this.isThinClientEnabled = isThinClientEnabled;
}

private async Task<AccountProperties> GetDatabaseAccountAsync(Uri serviceEndpoint)
Expand All @@ -57,6 +60,13 @@ await this.cosmosAuthorization.AddAuthorizationHeaderAsync(
resourceType: ResourceType.DatabaseAccount,
authorizationTokenType: AuthorizationTokenType.PrimaryMasterKey))
{
if (this.isThinClientEnabled)
{
headers.Add(
ThinClientConstants.EnableThinClientEndpointDiscoveryHeaderName,
this.isThinClientEnabled.ToString());
}

using (HttpResponseMessage responseMessage = await this.httpClient.GetAsync(
uri: serviceEndpoint,
additionalHeaders: headers,
Expand Down
23 changes: 17 additions & 6 deletions Microsoft.Azure.Cosmos/src/Resource/Settings/AccountProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public class AccountProperties
/// </summary>
internal AccountProperties()
{
this.ThinClientWritableLocationsInternal = new Collection<AccountRegion>();
this.ThinClientReadableLocationsInternal = new Collection<AccountRegion>();

this.QueryEngineConfigurationInternal = new Lazy<IDictionary<string, object>>(() => this.QueryStringToDictConverter());
}

Expand Down Expand Up @@ -127,6 +130,18 @@ internal Collection<AccountRegion> ReadLocationsInternal
set => this.readRegions = value;
}

/// <summary>
/// Gets or sets the set of ThinClient writable locations parsed from AdditionalProperties.
/// </summary>
[JsonIgnore]
Comment thread
FabianMeiswinkel marked this conversation as resolved.
internal Collection<AccountRegion> ThinClientWritableLocationsInternal { get; set; }

/// <summary>
/// Gets or sets the set of ThinClient readable locations parsed from AdditionalProperties.
/// </summary>
[JsonIgnore]
internal Collection<AccountRegion> ThinClientReadableLocationsInternal { get; set; }

/// <summary>
/// Gets the storage quota for media storage in the databaseAccount from the Azure Cosmos DB service.
/// </summary>
Expand Down Expand Up @@ -241,16 +256,12 @@ private IDictionary<string, object> QueryStringToDictConverter()
}
}

/// <summary>
/// This contains the thinclient endpoint value.
/// </summary>
internal Uri ThinClientEndpoint { get; set; }

/// <summary>
/// This contains additional values for scenarios where the SDK is not aware of new fields.
/// This ensures that if resource is read and updated none of the fields will be lost in the process.
/// </summary>
[JsonExtensionData]
internal IDictionary<string, JToken> AdditionalProperties { get; private set; }
internal IDictionary<string, JToken> AdditionalProperties { get; set; }

}
}
60 changes: 59 additions & 1 deletion Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ namespace Microsoft.Azure.Cosmos.Routing
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Common;
using Microsoft.Azure.Cosmos.Core.Trace;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents;
using Newtonsoft.Json.Linq;

/// <summary>
/// AddressCache implementation for client SDK. Supports cross region address routing based on
Expand Down Expand Up @@ -499,6 +500,52 @@ public void Dispose()
// that is never awaited on so it will not be thrown back to the caller.
this.cancellationTokenSource.Dispose();
}
}

/// <summary>
/// Parse thinClientWritableLocations / thinClientReadableLocations from AdditionalProperties.
/// </summary>
private static void ParseThinClientLocationsFromAdditionalProperties(AccountProperties databaseAccount)
{
if (databaseAccount?.AdditionalProperties != null)
{
if (databaseAccount.AdditionalProperties.TryGetValue("thinClientWritableLocations", out JToken writableToken)
&& writableToken is JArray writableArray)
{
databaseAccount.ThinClientWritableLocationsInternal = ParseAccountRegionArray(writableArray);
}

if (databaseAccount.AdditionalProperties.TryGetValue("thinClientReadableLocations", out JToken readableToken)
&& readableToken is JArray readableArray)
{
databaseAccount.ThinClientReadableLocationsInternal = ParseAccountRegionArray(readableArray);
}
}
}

private static Collection<AccountRegion> ParseAccountRegionArray(JArray array)
{
Collection<AccountRegion> result = new Collection<AccountRegion>();
foreach (JToken token in array)
{
if (token is not JObject obj)
{
continue;
}

string? regionName = obj["name"]?.ToString();
string? endpointStr = obj["databaseAccountEndpoint"]?.ToString();

if (!string.IsNullOrEmpty(regionName) && !string.IsNullOrEmpty(endpointStr))
{
result.Add(new AccountRegion
{
Name = regionName,
Endpoint = endpointStr
});
}
}
return result;
}

public virtual void InitializeAccountPropertiesAndStartBackgroundRefresh(AccountProperties databaseAccount)
Expand Down Expand Up @@ -662,6 +709,8 @@ private async Task RefreshDatabaseAccountInternalAsync(bool forceRefresh)
{
this.LastBackgroundRefreshUtc = DateTime.UtcNow;
AccountProperties accountProperties = await this.GetDatabaseAccountAsync(true);

GlobalEndpointManager.ParseThinClientLocationsFromAdditionalProperties(accountProperties);

this.locationCache.OnDatabaseAccountRead(accountProperties);

Expand Down Expand Up @@ -717,6 +766,15 @@ public IList<string> GetEffectivePreferredLocations()

return this.connectionPolicy.PreferredLocations?.Count > 0 ?
this.connectionPolicy.PreferredLocations : this.locationCache.EffectivePreferredLocations;
}

public Uri ResolveThinClientEndpoint(DocumentServiceRequest request)
{
bool isReadRequest = request.IsReadOnlyRequest
Comment thread
aavasthy marked this conversation as resolved.
|| request.OperationType == OperationType.Query
|| request.OperationType == OperationType.ReadFeed;

return this.locationCache.ResolveThinClientEndpoint(request, isReadRequest);
}
}
}
Loading
Loading