Skip to content

Commit da19e1a

Browse files
Moves Batch/Bulk to proxy endpoint when GW2.0 is enabled (#45945)
* Adding bulk/batch support for ThinClient * Update sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/implementation/ThinClientE2ETest.java Co-authored-by: Copilot <[email protected]> * Update ThinClientE2ETest.java * Update ClientSideRequestStatistics.java * Fixing bug when GW20 is used for Bulk with client-level consistency < Session --------- Co-authored-by: Copilot <[email protected]>
1 parent 0dca69d commit da19e1a

24 files changed

+370
-63
lines changed

sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/RetryContextOnDiagnosticTest.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,12 @@ public void backoffRetryUtilityExecuteRetry() throws Exception {
9898
ByteBuf buffer = getUTF8BytesOrNull(rawJson);
9999
Mockito.when(callbackMethod.call()).thenThrow(exception, exception, exception, exception, exception)
100100

101-
.thenReturn(Mono.just(new StoreResponse(200, new HashMap<>(), new ByteBufInputStream(buffer, true), buffer.readableBytes())));
101+
.thenReturn(Mono.just(new StoreResponse(
102+
null,
103+
200,
104+
new HashMap<>(),
105+
new ByteBufInputStream(buffer, true),
106+
buffer.readableBytes())));
102107
Mono<StoreResponse> monoResponse = BackoffRetryUtility.executeRetry(callbackMethod, retryPolicy);
103108
StoreResponse response = validateSuccess(monoResponse);
104109

@@ -143,7 +148,12 @@ public void backoffRetryUtilityExecuteAsync() {
143148
String rawJson = "{\"id\":\"" + responseText + "\"}";
144149
ByteBuf buffer = getUTF8BytesOrNull(rawJson);
145150
Mockito.when(parameterizedCallbackMethod.apply(ArgumentMatchers.any())).thenReturn(exceptionMono, exceptionMono, exceptionMono, exceptionMono, exceptionMono)
146-
.thenReturn(Mono.just(new StoreResponse(200, new HashMap<>(), new ByteBufInputStream(buffer, true), buffer.readableBytes())));
151+
.thenReturn(Mono.just(new StoreResponse(
152+
null,
153+
200,
154+
new HashMap<>(),
155+
new ByteBufInputStream(buffer, true),
156+
buffer.readableBytes())));
147157
Mono<StoreResponse> monoResponse = BackoffRetryUtility.executeAsync(
148158
parameterizedCallbackMethod,
149159
retryPolicy,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ public RetryContext getRetryContext() {
436436
private static RxDocumentServiceResponse mockRxDocumentServiceResponse(String content, Map<String, String> headers) {
437437
byte[] blob = content.getBytes(StandardCharsets.UTF_8);
438438
StoreResponse storeResponse = new StoreResponse(
439+
null,
439440
HttpResponseStatus.OK.code(),
440441
headers,
441442
new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true),

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ public StoreResponseBuilder withContent(String content) {
9595
public StoreResponse build() {
9696
ByteBuf buffer = getUTF8BytesOrNull(content);
9797
if (buffer == null) {
98-
return new StoreResponse(status, headers, null, 0);
98+
return new StoreResponse(null, status, headers, null, 0);
9999
}
100-
return new StoreResponse(status, headers, new ByteBufInputStream(buffer, true), buffer.readableBytes());
100+
return new StoreResponse(null, status, headers, new ByteBufInputStream(buffer, true), buffer.readableBytes());
101101
}
102102
}

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

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@
66
import com.azure.cosmos.CosmosAsyncClient;
77
import com.azure.cosmos.CosmosAsyncContainer;
88
import com.azure.cosmos.CosmosClientBuilder;
9+
import com.azure.cosmos.CosmosDiagnostics;
10+
import com.azure.cosmos.CosmosDiagnosticsContext;
11+
import com.azure.cosmos.CosmosDiagnosticsRequestInfo;
912
import com.azure.cosmos.FlakyTestRetryAnalyzer;
13+
import com.azure.cosmos.models.CosmosBatch;
14+
import com.azure.cosmos.models.CosmosBatchResponse;
15+
import com.azure.cosmos.models.CosmosBulkItemResponse;
16+
import com.azure.cosmos.models.CosmosBulkOperationResponse;
17+
import com.azure.cosmos.models.CosmosBulkOperations;
1018
import com.azure.cosmos.models.CosmosQueryRequestOptions;
1119
import com.azure.cosmos.models.PartitionKey;
1220
import com.azure.cosmos.models.SqlQuerySpec;
@@ -19,22 +27,31 @@
1927
import com.azure.cosmos.models.CosmosPatchOperations;
2028
import com.fasterxml.jackson.databind.ObjectMapper;
2129
import com.fasterxml.jackson.databind.node.ObjectNode;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
2232
import org.testng.annotations.Test;
33+
import reactor.core.publisher.Flux;
2334

2435
import java.util.Arrays;
36+
import java.util.Collection;
37+
import java.util.List;
2538
import java.util.UUID;
2639

2740
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
41+
import static org.assertj.core.api.Fail.fail;
2842

2943
// End to end sanity tests for basic thin client functionality.
3044
public class ThinClientE2ETest {
45+
private static final Logger logger = LoggerFactory.getLogger(ThinClientE2ETest.class);
46+
private static final String thinClientEndpointIndicator = ":10250/";
47+
3148
@Test(groups = {"thinclient"}, retryAnalyzer = FlakyTestRetryAnalyzer.class)
3249
public void testThinClientQuery() {
3350
CosmosAsyncClient client = null;
3451
try {
3552
// If running locally, uncomment these lines
36-
//System.setProperty("COSMOS.THINCLIENT_ENABLED", "true");
37-
//System.setProperty("COSMOS.HTTP2_ENABLED", "true");
53+
// System.setProperty("COSMOS.THINCLIENT_ENABLED", "true");
54+
// System.setProperty("COSMOS.HTTP2_ENABLED", "true");
3855

3956
client = new CosmosClientBuilder()
4057
.endpoint(TestConfigurations.HOST)
@@ -67,14 +84,131 @@ public void testThinClientQuery() {
6784
ObjectNode docFromResponse = response.getResults().get(0);
6885
assertThat(docFromResponse.get(partitionKeyName).textValue()).isEqualTo(idValue);
6986
assertThat(docFromResponse.get(idName).textValue()).isEqualTo(idValue);
87+
assertThinClientEndpointUsed(response.getCosmosDiagnostics());
88+
89+
} finally {
90+
if (client != null) {
91+
client.close();
92+
}
93+
}
94+
}
95+
96+
@Test(groups = {"thinclient"}, retryAnalyzer = FlakyTestRetryAnalyzer.class)
97+
public void testThinClientBulk() {
98+
CosmosAsyncClient client = null;
99+
try {
100+
// If running locally, uncomment these lines
101+
// System.setProperty("COSMOS.THINCLIENT_ENABLED", "true");
102+
// System.setProperty("COSMOS.HTTP2_ENABLED", "true");
103+
104+
client = new CosmosClientBuilder()
105+
.endpoint(TestConfigurations.HOST)
106+
.key(TestConfigurations.MASTER_KEY)
107+
.gatewayMode()
108+
.consistencyLevel(ConsistencyLevel.EVENTUAL)
109+
.buildAsyncClient();
110+
111+
CosmosAsyncContainer container = client.getDatabase("db1").getContainer("c2");
112+
String idName = "id";
113+
String partitionKeyName = "partitionKey";
114+
ObjectMapper mapper = new ObjectMapper();
115+
ObjectNode doc = mapper.createObjectNode();
116+
String idValue = UUID.randomUUID().toString();
117+
doc.put(idName, idValue);
118+
doc.put(partitionKeyName, idValue);
119+
120+
Flux<CosmosBulkOperationResponse<Object>> responsesFlux = container.executeBulkOperations(Flux.just(
121+
CosmosBulkOperations.getCreateItemOperation(doc, new PartitionKey(idValue))
122+
));
123+
124+
List<CosmosBulkOperationResponse<Object>> responses = responsesFlux.collectList().block();
125+
126+
assertThat(responses.size()).isEqualTo(1);
127+
assertThat(responses.get(0).getException()).isNull();
128+
CosmosBulkItemResponse bulkResponse = responses.get(0).getResponse();
129+
assertThat(bulkResponse.isSuccessStatusCode()).isEqualTo(true);
130+
assertThinClientEndpointUsed(bulkResponse.getCosmosDiagnostics());
131+
} finally {
132+
if (client != null) {
133+
client.close();
134+
}
135+
}
136+
}
137+
138+
@Test(groups = {"thinclient"}, retryAnalyzer = FlakyTestRetryAnalyzer.class)
139+
public void testThinClientBatch() {
140+
CosmosAsyncClient client = null;
141+
try {
142+
// If running locally, uncomment these lines
143+
// System.setProperty("COSMOS.THINCLIENT_ENABLED", "true");
144+
// System.setProperty("COSMOS.HTTP2_ENABLED", "true");
145+
146+
client = new CosmosClientBuilder()
147+
.endpoint(TestConfigurations.HOST)
148+
.key(TestConfigurations.MASTER_KEY)
149+
.gatewayMode()
150+
.consistencyLevel(ConsistencyLevel.SESSION)
151+
.buildAsyncClient();
70152

153+
CosmosAsyncContainer container = client.getDatabase("db1").getContainer("c2");
154+
String idName = "id";
155+
String partitionKeyName = "partitionKey";
156+
ObjectMapper mapper = new ObjectMapper();
157+
String pkValue = UUID.randomUUID().toString();
158+
ObjectNode doc1 = mapper.createObjectNode();
159+
String idValue1 = UUID.randomUUID().toString();
160+
doc1.put(idName, idValue1);
161+
doc1.put(partitionKeyName, pkValue);
162+
163+
ObjectNode doc2 = mapper.createObjectNode();
164+
String idValue2 = UUID.randomUUID().toString();
165+
doc2.put(idName, idValue2);
166+
doc2.put(partitionKeyName, pkValue);
167+
168+
CosmosBatch batch = CosmosBatch.createCosmosBatch(new PartitionKey(pkValue));
169+
batch.createItemOperation(doc1);
170+
batch.createItemOperation(doc2);
171+
172+
CosmosBatchResponse response = container
173+
.executeCosmosBatch(batch)
174+
.block();
175+
176+
assertThat(response.getStatusCode()).isEqualTo(200);
177+
assertThinClientEndpointUsed(response.getDiagnostics());
71178
} finally {
72179
if (client != null) {
73180
client.close();
74181
}
75182
}
76183
}
77184

185+
private static void assertThinClientEndpointUsed(CosmosDiagnostics diagnostics) {
186+
assertThat(diagnostics).isNotNull();
187+
188+
CosmosDiagnosticsContext ctx = diagnostics.getDiagnosticsContext();
189+
assertThat(ctx).isNotNull();
190+
191+
Collection<CosmosDiagnosticsRequestInfo> requests = ctx.getRequestInfo();
192+
assertThat(requests).isNotNull();
193+
assertThat(requests.size()).isPositive();
194+
195+
for (CosmosDiagnosticsRequestInfo requestInfo : requests) {
196+
logger.info(
197+
"Endpoint: {}, RequestType: {}, Partition: {}/{}, ActivityId: {}",
198+
requestInfo.getEndpoint(),
199+
requestInfo.getRequestType(),
200+
requestInfo.getPartitionId(),
201+
requestInfo.getPartitionKeyRangeId(),
202+
requestInfo.getActivityId());
203+
if (requestInfo.getEndpoint().contains(thinClientEndpointIndicator)) {
204+
return;
205+
}
206+
}
207+
208+
fail("No request targeting thin client proxy endpoint.");
209+
}
210+
211+
78212
@Test(groups = {"thinclient"}, retryAnalyzer = FlakyTestRetryAnalyzer.class)
79213
public void testThinClientDocumentPointOperations() {
80214
CosmosAsyncClient client = null;
@@ -113,11 +247,13 @@ public void testThinClientDocumentPointOperations() {
113247
CosmosItemResponse<ObjectNode> createResponse = container.createItem(doc).block();
114248
assertThat(createResponse.getStatusCode()).isEqualTo(201);
115249
assertThat(createResponse.getRequestCharge()).isGreaterThan(0.0);
250+
assertThinClientEndpointUsed(createResponse.getDiagnostics());
116251

117252
// read
118253
CosmosItemResponse<ObjectNode> readResponse = container.readItem(idValue, new PartitionKey(idValue), ObjectNode.class).block();
119254
assertThat(readResponse.getStatusCode()).isEqualTo(200);
120255
assertThat(readResponse.getRequestCharge()).isGreaterThan(0.0);
256+
assertThinClientEndpointUsed(readResponse.getDiagnostics());
121257

122258
ObjectNode doc2 = mapper.createObjectNode();
123259
String idValue2 = UUID.randomUUID().toString();
@@ -128,11 +264,14 @@ public void testThinClientDocumentPointOperations() {
128264
CosmosItemResponse<ObjectNode> replaceResponse = container.replaceItem(doc2, idValue, new PartitionKey(idValue)).block();
129265
assertThat(replaceResponse.getStatusCode()).isEqualTo(200);
130266
assertThat(replaceResponse.getRequestCharge()).isGreaterThan(0.0);
267+
assertThinClientEndpointUsed(replaceResponse.getDiagnostics());
268+
131269
CosmosItemResponse<ObjectNode> readAfterReplaceResponse = container.readItem(idValue2, new PartitionKey(idValue), ObjectNode.class).block();
132270
assertThat(readAfterReplaceResponse.getStatusCode()).isEqualTo(200);
133271
ObjectNode replacedItemFromRead = readAfterReplaceResponse.getItem();
134272
assertThat(replacedItemFromRead.get(idName).asText()).isEqualTo(idValue2);
135273
assertThat(replacedItemFromRead.get(partitionKeyName).asText()).isEqualTo(idValue);
274+
assertThinClientEndpointUsed(readAfterReplaceResponse.getDiagnostics());
136275

137276
ObjectNode doc3 = mapper.createObjectNode();
138277
doc3.put(idName, idValue2);
@@ -143,11 +282,14 @@ public void testThinClientDocumentPointOperations() {
143282
CosmosItemResponse<ObjectNode> upsertResponse = container.upsertItem(doc3, new PartitionKey(idValue), new CosmosItemRequestOptions()).block();
144283
assertThat(upsertResponse.getStatusCode()).isEqualTo(200);
145284
assertThat(upsertResponse.getRequestCharge()).isGreaterThan(0.0);
285+
assertThinClientEndpointUsed(upsertResponse.getDiagnostics());
286+
146287
CosmosItemResponse<ObjectNode> readAfterUpsertResponse = container.readItem(idValue2, new PartitionKey(idValue), ObjectNode.class).block();
147288
ObjectNode upsertedItemFromRead = readAfterUpsertResponse.getItem();
148289
assertThat(upsertedItemFromRead.get(idName).asText()).isEqualTo(idValue2);
149290
assertThat(upsertedItemFromRead.get(partitionKeyName).asText()).isEqualTo(idValue);
150291
assertThat(upsertedItemFromRead.get("newField").asText()).isEqualTo("newValue");
292+
assertThinClientEndpointUsed(readAfterUpsertResponse.getDiagnostics());
151293

152294
// patch
153295
CosmosPatchOperations patchOperations = CosmosPatchOperations.create();
@@ -156,17 +298,21 @@ public void testThinClientDocumentPointOperations() {
156298
CosmosItemResponse<ObjectNode> patchResponse = container.patchItem(idValue2, new PartitionKey(idValue), patchOperations, ObjectNode.class).block();
157299
assertThat(patchResponse.getStatusCode()).isEqualTo(200);
158300
assertThat(patchResponse.getRequestCharge()).isGreaterThan(0.0);
301+
assertThinClientEndpointUsed(patchResponse.getDiagnostics());
302+
159303
CosmosItemResponse<ObjectNode> readAfterPatchResponse = container.readItem(idValue2, new PartitionKey(idValue), ObjectNode.class).block();
160304
ObjectNode patchedItemFromRead = readAfterPatchResponse.getItem();
161305
assertThat(patchedItemFromRead.get(idName).asText()).isEqualTo(idValue2);
162306
assertThat(patchedItemFromRead.get(partitionKeyName).asText()).isEqualTo(idValue);
163307
assertThat(patchedItemFromRead.get("newField").asText()).isEqualTo("patchedNewField");
164308
assertThat(patchedItemFromRead.get("anotherNewField").asText()).isEqualTo("anotherNewValue");
309+
assertThinClientEndpointUsed(readAfterPatchResponse.getDiagnostics());
165310

166311
// delete
167312
CosmosItemResponse<Object> deleteResponse = container.deleteItem(idValue2, new PartitionKey(idValue)).block();
168313
assertThat(deleteResponse.getStatusCode()).isEqualTo(204);
169314
assertThat(deleteResponse.getRequestCharge()).isGreaterThan(0.0);
315+
assertThinClientEndpointUsed(deleteResponse.getDiagnostics());
170316
} finally {
171317
if (client != null) {
172318
client.close();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public void validateAllSetValuesInCosmosBulkItemResponse() {
7979

8080
byte[] blob = responseContent.getBytes(StandardCharsets.UTF_8);
8181
StoreResponse storeResponse = new StoreResponse(
82+
null,
8283
HttpResponseStatus.OK.code(),
8384
headers,
8485
new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true),
@@ -161,6 +162,7 @@ public void validateEmptyHeaderInCosmosBulkItemResponse() {
161162

162163
byte[] blob = responseContent.getBytes(StandardCharsets.UTF_8);
163164
StoreResponse storeResponse = new StoreResponse(
165+
null,
164166
HttpResponseStatus.OK.code(),
165167
new HashMap<>(),
166168
new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true),

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public void validateAllSetValuesInResponse() {
7575

7676
byte[] blob = responseContent.getBytes(StandardCharsets.UTF_8);
7777
StoreResponse storeResponse = new StoreResponse(
78+
null,
7879
HttpResponseStatus.OK.code(),
7980
headers,
8081
new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true),
@@ -138,6 +139,7 @@ public void validateEmptyHeaderInResponse() {
138139

139140
byte[] blob = responseContent.getBytes(StandardCharsets.UTF_8);
140141
StoreResponse storeResponse = new StoreResponse(
142+
null,
141143
HttpResponseStatus.OK.code(),
142144
new HashMap<>(),
143145
new ByteBufInputStream(Unpooled.wrappedBuffer(blob), true),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public void getLsnAndGlobalCommittedLsn() {
213213
headers.put(WFConstants.BackendHeaders.LSN, "3");
214214
headers.put(WFConstants.BackendHeaders.GLOBAL_COMMITTED_LSN, "2");
215215

216-
StoreResponse sr = new StoreResponse(0, headers, null, 0);
216+
StoreResponse sr = new StoreResponse(null, 0, headers, null, 0);
217217
Utils.ValueHolder<Long> lsn = Utils.ValueHolder.initialize(-2l);
218218
Utils.ValueHolder<Long> globalCommittedLsn = Utils.ValueHolder.initialize(-2l);
219219
ConsistencyWriter.getLsnAndGlobalCommittedLsn(sr, lsn, globalCommittedLsn);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void stringContent() {
2222
headerMap.put("key2", "value2");
2323

2424
ByteBuf buffer = getUTF8BytesOrNull(jsonContent);
25-
StoreResponse sp = new StoreResponse(200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes());
25+
StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes());
2626

2727
assertThat(sp.getStatus()).isEqualTo(200);
2828
assertThat(sp.getResponseBodyAsJson().get("id").asText()).isEqualTo(content);
@@ -39,7 +39,7 @@ public void headerNamesAreCaseInsensitive() {
3939
headerMap.put("KEY3", "value3");
4040

4141
ByteBuf buffer = getUTF8BytesOrNull(jsonContent);
42-
StoreResponse sp = new StoreResponse(200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes());
42+
StoreResponse sp = new StoreResponse(null, 200, headerMap, new ByteBufInputStream(buffer, true), buffer.readableBytes());
4343

4444
assertThat(sp.getStatus()).isEqualTo(200);
4545
assertThat(sp.getResponseBodyAsJson().get("id").asText()).isEqualTo(content);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public StoreResultDiagnosticsSerializerTests() throws IOException {
3737
//TODO: add more test cases
3838
@Test(groups = "unit")
3939
public void storeResultDiagnosticsSerializerTests() {
40-
StoreResponse storeResponse = new StoreResponse(200, new HashMap<>(), null, 0);
40+
StoreResponse storeResponse = new StoreResponse(null, 200, new HashMap<>(), null, 0);
4141
StoreResult storeResult = new StoreResult(
4242
storeResponse,
4343
null,

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/CosmosDiagnosticsContext.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,8 @@ private static void addRequestInfoForGatewayStatistics(
783783
gatewayStats.getResponsePayloadSizeInBytes(),
784784
gatewayStats.getStatusCode(),
785785
gatewayStats.getSubStatusCode(),
786-
new ArrayList<>()
786+
new ArrayList<>(),
787+
gatewayStats.getEndpoint()
787788
);
788789

789790
requestInfo.add(info);
@@ -851,7 +852,8 @@ private static void addRequestInfoForStoreResponses(
851852
responsePayloadLength,
852853
statusCode,
853854
subStatusCode,
854-
events
855+
events,
856+
resultDiagnostics.getStorePhysicalAddressAsString()
855857
);
856858

857859
requestInfo.add(info);
@@ -914,7 +916,8 @@ private void addRequestInfoForAddressResolution(
914916
0,
915917
0,
916918
0,
917-
new ArrayList<>()
919+
new ArrayList<>(),
920+
addressResolutionStatistics.getTargetEndpoint()
918921
);
919922

920923
requestInfo.add(info);

0 commit comments

Comments
 (0)