Skip to content

Commit 668d00c

Browse files
authored
ThinClient E2E Integration (Azure#44854)
* add changes for thin client e2e integration. * pr comments * pr comments * pr comments * remove redundant metadata check from earlier causing direct mode tests to fail * add thinclient profile and stage to live tests * modify useThinClient() * pr comments
1 parent 1d16959 commit 668d00c

19 files changed

+433
-38
lines changed

sdk/cosmos/azure-cosmos-tests/pom.xml

+21
Original file line numberDiff line numberDiff line change
@@ -663,5 +663,26 @@ Licensed under the MIT License.
663663
</plugins>
664664
</build>
665665
</profile>
666+
<profile>
667+
<!-- thin client integration tests, requires thin client endpoint and key -->
668+
<id>thinclient</id>
669+
<properties>
670+
<test.groups>thinclient</test.groups>
671+
</properties>
672+
<build>
673+
<plugins>
674+
<plugin>
675+
<groupId>org.apache.maven.plugins</groupId>
676+
<artifactId>maven-failsafe-plugin</artifactId>
677+
<version>3.5.2</version> <!-- {x-version-update;org.apache.maven.plugins:maven-failsafe-plugin;external_dependency} -->
678+
<configuration>
679+
<suiteXmlFiles>
680+
<suiteXmlFile>src/test/resources/thinclient-testng.xml</suiteXmlFile>
681+
</suiteXmlFiles>
682+
</configuration>
683+
</plugin>
684+
</plugins>
685+
</build>
686+
</profile>
666687
</profiles>
667688
</project>

sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/ConfigsTests.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.net.URI;
1212
import java.util.EnumSet;
1313

14+
import static com.azure.cosmos.implementation.Configs.isThinClientEnabled;
1415
import static org.assertj.core.api.Assertions.assertThat;
1516

1617
public class ConfigsTests {
@@ -165,21 +166,19 @@ public void http2MaxConcurrentStreams() {
165166
}
166167
}
167168

168-
@Test(groups = { "unit" })
169+
@Test(groups = { "emulator" })
169170
public void thinClientEnabledTest() {
170-
Configs config = new Configs();
171-
assertThat(config.getThinclientEnabled()).isFalse();
172-
171+
assertThat(isThinClientEnabled()).isFalse();
173172
System.clearProperty("COSMOS.THINCLIENT_ENABLED");
174173
System.setProperty("COSMOS.THINCLIENT_ENABLED", "true");
175174
try {
176-
assertThat(config.getThinclientEnabled()).isTrue();
175+
assertThat(isThinClientEnabled()).isTrue();
177176
} finally {
178177
System.clearProperty("COSMOS.THINCLIENT_ENABLED");
179178
}
180179
}
181180

182-
@Test(groups = { "unit" })
181+
@Test(groups = { "emulator" })
183182
public void thinClientEndpointTest() {
184183
Configs config = new Configs();
185184
assertThat(config.getThinclientEndpoint()).isEqualTo(URI.create(""));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.azure.cosmos.implementation;
4+
5+
import com.azure.cosmos.ConsistencyLevel;
6+
import com.azure.cosmos.CosmosAsyncClient;
7+
import com.azure.cosmos.CosmosAsyncContainer;
8+
import com.azure.cosmos.CosmosClientBuilder;
9+
import com.azure.cosmos.models.CosmosItemRequestOptions;
10+
import com.azure.cosmos.models.CosmosItemResponse;
11+
import com.azure.cosmos.models.CosmosPatchOperations;
12+
import com.azure.cosmos.models.PartitionKey;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
14+
import com.fasterxml.jackson.databind.node.ObjectNode;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
import org.testng.annotations.Test;
18+
19+
import java.util.UUID;
20+
21+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
22+
23+
public class ThinClientE2ETest extends com.azure.cosmos.rx.TestSuiteBase {
24+
@Test(groups = {"thinclient"})
25+
public void testThinClientDocumentPointOperations() {
26+
CosmosAsyncClient client = null;
27+
try {
28+
System.setProperty("COSMOS.THINCLIENT_ENABLED", "true");
29+
System.setProperty("COSMOS.HTTP2_ENABLED", "true");
30+
31+
String thinclientTestEndpoint = System.getProperty("COSMOS.THINCLIENT_ENDPOINT");
32+
String thinclientTestKey = System.getProperty("COSMOS.THINCLIENT_KEY");
33+
34+
client = new CosmosClientBuilder()
35+
.endpoint(thinclientTestEndpoint)
36+
.key(thinclientTestKey)
37+
.gatewayMode()
38+
.consistencyLevel(ConsistencyLevel.SESSION)
39+
.buildAsyncClient();
40+
41+
CosmosAsyncContainer container = client.getDatabase("updatedd-thin-client-test-db").getContainer("thin-client-test-container-1");
42+
ObjectMapper mapper = new ObjectMapper();
43+
ObjectNode doc = mapper.createObjectNode();
44+
String idValue = UUID.randomUUID().toString();
45+
doc.put("id", idValue);
46+
doc.put("pk", idValue);
47+
48+
// create
49+
CosmosItemResponse<ObjectNode> createResponse = container.createItem(doc).block();
50+
assertThat(createResponse.getStatusCode()).isEqualTo(201);
51+
assertThat(createResponse.getRequestCharge()).isGreaterThan(0.0);
52+
53+
// read
54+
CosmosItemResponse<ObjectNode> readResponse = container.readItem(idValue, new PartitionKey(idValue), ObjectNode.class).block();
55+
assertThat(readResponse.getStatusCode()).isEqualTo(200);
56+
assertThat(readResponse.getRequestCharge()).isGreaterThan(0.0);
57+
58+
ObjectNode doc2 = mapper.createObjectNode();
59+
String idValue2 = UUID.randomUUID().toString();
60+
doc2.put("id", idValue2);
61+
doc2.put("pk", idValue);
62+
63+
// replace
64+
CosmosItemResponse<ObjectNode> replaceResponse = container.replaceItem(doc2, idValue, new PartitionKey(idValue)).block();
65+
assertThat(replaceResponse.getStatusCode()).isEqualTo(200);
66+
assertThat(replaceResponse.getRequestCharge()).isGreaterThan(0.0);
67+
CosmosItemResponse<ObjectNode> readAfterReplaceResponse = container.readItem(idValue2, new PartitionKey(idValue), ObjectNode.class).block();
68+
assertThat(readAfterReplaceResponse.getStatusCode()).isEqualTo(200);
69+
ObjectNode replacedItemFromRead = readAfterReplaceResponse.getItem();
70+
assertThat(replacedItemFromRead.get("id").asText()).isEqualTo(idValue2);
71+
assertThat(replacedItemFromRead.get("pk").asText()).isEqualTo(idValue);
72+
73+
ObjectNode doc3 = mapper.createObjectNode();
74+
doc3.put("id", idValue2);
75+
doc3.put("pk", idValue);
76+
doc3.put("newField", "newValue");
77+
78+
// upsert
79+
CosmosItemResponse<ObjectNode> upsertResponse = container.upsertItem(doc3, new PartitionKey(idValue), new CosmosItemRequestOptions()).block();
80+
assertThat(upsertResponse.getStatusCode()).isEqualTo(200);
81+
assertThat(upsertResponse.getRequestCharge()).isGreaterThan(0.0);
82+
CosmosItemResponse<ObjectNode> readAfterUpsertResponse = container.readItem(idValue2, new PartitionKey(idValue), ObjectNode.class).block();
83+
ObjectNode upsertedItemFromRead = readAfterUpsertResponse.getItem();
84+
assertThat(upsertedItemFromRead.get("id").asText()).isEqualTo(idValue2);
85+
assertThat(upsertedItemFromRead.get("pk").asText()).isEqualTo(idValue);
86+
assertThat(upsertedItemFromRead.get("newField").asText()).isEqualTo("newValue");
87+
88+
// patch
89+
CosmosPatchOperations patchOperations = CosmosPatchOperations.create();
90+
patchOperations.add("/anotherNewField", "anotherNewValue");
91+
patchOperations.replace("/newField", "patchedNewField");
92+
CosmosItemResponse<ObjectNode> patchResponse = container.patchItem(idValue2, new PartitionKey(idValue), patchOperations, ObjectNode.class).block();
93+
assertThat(patchResponse.getStatusCode()).isEqualTo(200);
94+
assertThat(patchResponse.getRequestCharge()).isGreaterThan(0.0);
95+
CosmosItemResponse<ObjectNode> readAfterPatchResponse = container.readItem(idValue2, new PartitionKey(idValue), ObjectNode.class).block();
96+
ObjectNode patchedItemFromRead = readAfterPatchResponse.getItem();
97+
assertThat(patchedItemFromRead.get("id").asText()).isEqualTo(idValue2);
98+
assertThat(patchedItemFromRead.get("pk").asText()).isEqualTo(idValue);
99+
assertThat(patchedItemFromRead.get("newField").asText()).isEqualTo("patchedNewField");
100+
assertThat(patchedItemFromRead.get("anotherNewField").asText()).isEqualTo("anotherNewValue");
101+
102+
// delete
103+
CosmosItemResponse<Object> deleteResponse = container.deleteItem(idValue2, new PartitionKey(idValue)).block();
104+
assertThat(deleteResponse.getStatusCode()).isEqualTo(204);
105+
assertThat(deleteResponse.getRequestCharge()).isGreaterThan(0.0);
106+
} finally {
107+
System.clearProperty("COSMOS.THINCLIENT_ENABLED");
108+
System.clearProperty("COSMOS.HTTP2_ENABLED");
109+
client.close();
110+
}
111+
}
112+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!--
2+
~ The MIT License (MIT)
3+
~ Copyright (c) 2018 Microsoft Corporation
4+
~
5+
~ Permission is hereby granted, free of charge, to any person obtaining a copy
6+
~ of this software and associated documentation files (the "Software"), to deal
7+
~ in the Software without restriction, including without limitation the rights
8+
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
~ copies of the Software, and to permit persons to whom the Software is
10+
~ furnished to do so, subject to the following conditions:
11+
~
12+
~ The above copyright notice and this permission notice shall be included in all
13+
~ copies or substantial portions of the Software.
14+
~
15+
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
~ SOFTWARE.
22+
-->
23+
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
24+
<suite name="thinclient">
25+
<test name="thinclient" group-by-instances="true">
26+
<groups>
27+
<run>
28+
<include name="thinclient"/>
29+
</run>
30+
</groups>
31+
<packages>
32+
<package name="com.azure.cosmos.*"/>
33+
</packages>
34+
</test>
35+
</suite>

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Configs.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ public URI getThinclientEndpoint() {
434434
return URI.create(DEFAULT_THINCLIENT_ENDPOINT);
435435
}
436436

437-
public static boolean getThinclientEnabled() {
437+
public static boolean isThinClientEnabled() {
438438
String valueFromSystemProperty = System.getProperty(THINCLIENT_ENABLED);
439439
if (valueFromSystemProperty != null && !valueFromSystemProperty.isEmpty()) {
440440
return Boolean.parseBoolean(valueFromSystemProperty);

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/Constants.java

+2
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ public static final class Properties {
199199
public static final String Name = "name";
200200
public static final String WRITABLE_LOCATIONS = "writableLocations";
201201
public static final String READABLE_LOCATIONS = "readableLocations";
202+
public static final String THINCLIENT_WRITABLE_LOCATIONS = "thinClientWritableLocations";
203+
public static final String THINCLIENT_READABLE_LOCATIONS = "thinClientReadableLocations";
202204
public static final String DATABASE_ACCOUNT_ENDPOINT = "databaseAccountEndpoint";
203205

204206
//Authorization

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/DatabaseAccount.java

+18
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,24 @@ public void setReadableLocations(Iterable<DatabaseAccountLocation> locations) {
251251
this.set(Constants.Properties.READABLE_LOCATIONS, locations);
252252
}
253253

254+
/**
255+
* Gets the list of thin client readable locations for this database account.
256+
*
257+
* @return the list of thin client readable locations.
258+
*/
259+
public Iterable<DatabaseAccountLocation> getThinClientReadableLocations() {
260+
return super.getCollection(Constants.Properties.THINCLIENT_READABLE_LOCATIONS, DatabaseAccountLocation.class);
261+
}
262+
263+
/**
264+
* Gets the list of thin client writable locations for this database account.
265+
*
266+
* @return the list of thin client writable locations.
267+
*/
268+
public Iterable<DatabaseAccountLocation> getThinClientWritableLocations() {
269+
return super.getCollection(Constants.Properties.THINCLIENT_WRITABLE_LOCATIONS, DatabaseAccountLocation.class);
270+
}
271+
254272
/**
255273
* Gets if enable multiple write locations is set.
256274
*

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ public static class HttpHeaders {
286286
// Thinclient headers
287287
public static final String THINCLIENT_PROXY_OPERATION_TYPE = "x-ms-thinclient-proxy-operation-type";
288288
public static final String THINCLIENT_PROXY_RESOURCE_TYPE = "x-ms-thinclient-proxy-resource-type";
289-
289+
public static final String THINCLIENT_OPT_IN = "x-ms-cosmos-use-thinclient";
290290
public static final String GLOBAL_DATABASE_ACCOUNT_NAME = "GlobalDatabaseAccountName";
291291
}
292292

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java

+48
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ public class RxDocumentClientImpl implements AsyncDocumentClient, IAuthorization
211211
private String firstResourceTokenFromPermissionFeed = StringUtils.EMPTY;
212212
private RxClientCollectionCache collectionCache;
213213
private RxGatewayStoreModel gatewayProxy;
214+
private RxGatewayStoreModel thinProxy;
214215
private RxStoreModel storeModel;
215216
private GlobalAddressResolver addressResolver;
216217
private RxPartitionKeyRangeCache partitionKeyRangeCache;
@@ -664,6 +665,14 @@ private void updateGatewayProxy() {
664665
(this.gatewayProxy).setSessionContainer(this.sessionContainer);
665666
}
666667

668+
private void updateThinProxy() {
669+
(this.thinProxy).setGatewayServiceConfigurationReader(this.gatewayConfigurationReader);
670+
(this.thinProxy).setCollectionCache(this.collectionCache);
671+
(this.thinProxy).setPartitionKeyRangeCache(this.partitionKeyRangeCache);
672+
(this.thinProxy).setUseMultipleWriteLocations(this.useMultipleWriteLocations);
673+
(this.thinProxy).setSessionContainer(this.sessionContainer);
674+
}
675+
667676
public void init(CosmosClientMetadataCachesSnapshot metadataCachesSnapshot, Function<HttpClient, HttpClient> httpClientInterceptor) {
668677
try {
669678

@@ -680,6 +689,12 @@ public void init(CosmosClientMetadataCachesSnapshot metadataCachesSnapshot, Func
680689
this.reactorHttpClient,
681690
this.apiType);
682691

692+
this.thinProxy = createThinProxy(this.sessionContainer,
693+
this.consistencyLevel,
694+
this.userAgentContainer,
695+
this.globalEndpointManager,
696+
this.reactorHttpClient);
697+
683698
this.globalEndpointManager.init();
684699

685700
DatabaseAccount databaseAccountSnapshot = this.initializeGatewayConfigurationReader();
@@ -707,6 +722,7 @@ public void init(CosmosClientMetadataCachesSnapshot metadataCachesSnapshot, Func
707722
collectionCache);
708723

709724
updateGatewayProxy();
725+
updateThinProxy();
710726
clientTelemetry = new ClientTelemetry(
711727
this,
712728
null,
@@ -821,6 +837,20 @@ RxGatewayStoreModel createRxGatewayProxy(ISessionContainer sessionContainer,
821837
apiType);
822838
}
823839

840+
ThinClientStoreModel createThinProxy(ISessionContainer sessionContainer,
841+
ConsistencyLevel consistencyLevel,
842+
UserAgentContainer userAgentContainer,
843+
GlobalEndpointManager globalEndpointManager,
844+
HttpClient httpClient) {
845+
return new ThinClientStoreModel(
846+
this,
847+
sessionContainer,
848+
consistencyLevel,
849+
userAgentContainer,
850+
globalEndpointManager,
851+
httpClient);
852+
}
853+
824854
private HttpClient httpClient() {
825855
HttpClientConfig httpClientConfig = new HttpClientConfig(this.configs)
826856
.withMaxIdleConnectionTimeout(this.connectionPolicy.getIdleHttpConnectionTimeout())
@@ -5690,6 +5720,10 @@ public Flux<DatabaseAccount> getDatabaseAccountFromEndpoint(URI endpoint) {
56905720
return Flux.defer(() -> {
56915721
RxDocumentServiceRequest request = RxDocumentServiceRequest.create(this,
56925722
OperationType.Read, ResourceType.DatabaseAccount, "", null, (Object) null);
5723+
// if thin client enabled, populate thin client header so we can get thin client read and writeable locations
5724+
if (useThinClient()) {
5725+
request.getHeaders().put(HttpConstants.HttpHeaders.THINCLIENT_OPT_IN, "true");
5726+
}
56935727
return this.populateHeadersAsync(request, RequestVerb.GET)
56945728
.flatMap(requestPopulated -> {
56955729

@@ -5721,6 +5755,10 @@ private RxStoreModel getStoreProxy(RxDocumentServiceRequest request) {
57215755
return this.gatewayProxy;
57225756
}
57235757

5758+
if (useThinClientStoreModel(request)) {
5759+
return this.thinProxy;
5760+
}
5761+
57245762
ResourceType resourceType = request.getResourceType();
57255763
OperationType operationType = request.getOperationType();
57265764

@@ -6746,6 +6784,16 @@ private void handleLocationCancellationExceptionForPartitionKeyRange(RxDocumentS
67466784
}
67476785
}
67486786

6787+
private boolean useThinClient() {
6788+
return Configs.isThinClientEnabled() && this.connectionPolicy.getConnectionMode() == ConnectionMode.GATEWAY;
6789+
}
6790+
6791+
private boolean useThinClientStoreModel(RxDocumentServiceRequest request) {
6792+
return useThinClient()
6793+
&& request.getResourceType() == ResourceType.Document
6794+
&& request.getOperationType().isPointOperation();
6795+
}
6796+
67496797
@FunctionalInterface
67506798
private interface DocumentPointOperation {
67516799
Mono<ResourceResponse<Document>> apply(

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentServiceRequest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1185,9 +1185,11 @@ public void setEffectivePartitionKey(String effectivePartitionKey) {
11851185
this.effectivePartitionKey = effectivePartitionKey;
11861186
}
11871187

1188-
public void setThinclientHeaders(String operationType, String resourceType) {
1188+
public void setThinclientHeaders(String operationType, String resourceType, String globalDatabaseAccountName, String resourceId) {
11891189
this.headers.put(HttpConstants.HttpHeaders.THINCLIENT_PROXY_OPERATION_TYPE, operationType);
11901190
this.headers.put(HttpConstants.HttpHeaders.THINCLIENT_PROXY_RESOURCE_TYPE, resourceType);
1191+
this.headers.put(HttpConstants.HttpHeaders.GLOBAL_DATABASE_ACCOUNT_NAME, globalDatabaseAccountName);
1192+
this.headers.put(WFConstants.BackendHeaders.COLLECTION_RID, resourceId);
11911193
}
11921194

11931195
public RxDocumentServiceRequest setHttpTransportSerializer(HttpTransportSerializer transportSerializer) {

0 commit comments

Comments
 (0)