diff --git a/NuGet.Config b/NuGet.Config
new file mode 100644
index 000000000000..783c2dc749b7
--- /dev/null
+++ b/NuGet.Config
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eng/common/testproxy/test-proxy-tool.yml b/eng/common/testproxy/test-proxy-tool.yml
index 03c9dbaa00c1..2458f80699c1 100644
--- a/eng/common/testproxy/test-proxy-tool.yml
+++ b/eng/common/testproxy/test-proxy-tool.yml
@@ -5,6 +5,7 @@ parameters:
targetVersion: ''
templateRoot: '$(Build.SourcesDirectory)'
condition: true
+ proxyUrl: 'http://localhost:5000'
steps:
- pwsh: |
@@ -67,14 +68,14 @@ steps:
- pwsh: |
$invocation = @"
Start-Process $(Build.BinariesDirectory)/test-proxy/test-proxy.exe
- -ArgumentList `"start -u --storage-location ${{ parameters.rootFolder }}`"
+ -ArgumentList `"start -u --storage-location ${{ parameters.rootFolder }} -- --urls ${{ parameters.proxyUrl }}`"
-NoNewWindow -PassThru -RedirectStandardOutput ${{ parameters.rootFolder }}/test-proxy.log
-RedirectStandardError ${{ parameters.rootFolder }}/test-proxy-error.log
"@
Write-Host $invocation
$Process = Start-Process $(Build.BinariesDirectory)/test-proxy/test-proxy.exe `
- -ArgumentList "start -u --storage-location ${{ parameters.rootFolder }}" `
+ -ArgumentList "start -u --storage-location ${{ parameters.rootFolder }} -- --urls ${{ parameters.proxyUrl }}" `
-NoNewWindow -PassThru -RedirectStandardOutput ${{ parameters.rootFolder }}/test-proxy.log `
-RedirectStandardError ${{ parameters.rootFolder }}/test-proxy-error.log
@@ -87,7 +88,10 @@ steps:
# nohup does NOT continue beyond the current session if you use it within powershell
- bash: |
- nohup $(Build.BinariesDirectory)/test-proxy/test-proxy 1>${{ parameters.rootFolder }}/test-proxy.log 2>${{ parameters.rootFolder }}/test-proxy-error.log &
+ if [[ "$(uname)" == "Darwin" ]]; then
+ export DOTNET_ROOT="$HOME/.dotnet"
+ fi
+ nohup $(Build.BinariesDirectory)/test-proxy/test-proxy start -u --storage-location ${{ parameters.rootFolder }} -- --urls "${{ parameters.proxyUrl }}" 1>${{ parameters.rootFolder }}/test-proxy.log 2>${{ parameters.rootFolder }}/test-proxy-error.log &
echo $! > $(Build.SourcesDirectory)/test-proxy.pid
@@ -102,8 +106,8 @@ steps:
- pwsh: |
for ($i = 0; $i -lt 10; $i++) {
try {
- Write-Host "Invoke-WebRequest -Uri `"http://localhost:5000/Admin/IsAlive`" | Out-Null"
- Invoke-WebRequest -Uri "http://localhost:5000/Admin/IsAlive" | Out-Null
+ Write-Host "Invoke-WebRequest -Uri `"${{ parameters.proxyUrl }}/Admin/IsAlive`" | Out-Null"
+ Invoke-WebRequest -Uri "${{ parameters.proxyUrl }}/Admin/IsAlive" | Out-Null
Write-Host "Successfully connected to the test proxy on port 5000."
exit 0
} catch {
diff --git a/eng/pipelines/templates/jobs/ci.tests.yml b/eng/pipelines/templates/jobs/ci.tests.yml
index fdf148f93e98..82d12ff45fdf 100644
--- a/eng/pipelines/templates/jobs/ci.tests.yml
+++ b/eng/pipelines/templates/jobs/ci.tests.yml
@@ -127,6 +127,9 @@ jobs:
- ${{ parameters.PreTestSteps }}
+ # Authenticate with Azure Artifacts
+ - template: /eng/pipelines/templates/steps/maven-authenticate.yml
+
- template: /eng/pipelines/templates/steps/run-and-validate-linting.yml
parameters:
JavaBuildVersion: $(JavaTestVersion)
diff --git a/eng/pipelines/templates/jobs/ci.yml b/eng/pipelines/templates/jobs/ci.yml
index a44742b8a082..9cbc6a472be0 100644
--- a/eng/pipelines/templates/jobs/ci.yml
+++ b/eng/pipelines/templates/jobs/ci.yml
@@ -115,6 +115,9 @@ jobs:
ServiceDirectory: ${{parameters.ServiceDirectory}}
ExcludePaths: ${{parameters.ExcludePaths}}
+ # Authenticate with Azure Artifacts
+ - template: /eng/pipelines/templates/steps/maven-authenticate.yml
+
- task: UsePythonVersion@0
displayName: 'Use Python $(PythonVersion)'
inputs:
@@ -191,6 +194,11 @@ jobs:
parameters:
PackagePropertiesFolder: $(Build.ArtifactStagingDirectory)/PackageInfo
+ - task: PipAuthenticate@1
+ displayName: 'Pip Authenticate to Azure Artifacts'
+ inputs:
+ artifactFeeds: 'public/azure-sdk-for-python'
+
- script: |
python -m pip install markdown2==2.4.6 BeautifulSoup4==4.11.1
displayName: 'pip install markdown2 and BeautifulSoup4'
@@ -402,6 +410,11 @@ jobs:
version: 22.x
displayName: Use Node.js 22.x
+ - template: /eng/common/pipelines/templates/steps/create-authenticated-npmrc.yml
+ parameters:
+ npmrcPath: $(Agent.TempDirectory)/analyze-job/.npmrc
+ registryUrl: https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/
+
- task: PowerShell@2
displayName: Verify Swagger and TypeSpec Code Generation
inputs:
@@ -410,6 +423,11 @@ jobs:
arguments: >
-ServiceDirectories '$(PRServiceDirectories)'
-RegenerationType 'All'
+ env:
+ npm_config_userconfig: $(Agent.TempDirectory)/analyze-job/.npmrc
+
+ # Authenticate with Azure Artifacts
+ - template: /eng/pipelines/templates/steps/maven-authenticate.yml
- template: /eng/pipelines/templates/steps/run-and-validate-linting.yml
parameters:
diff --git a/eng/pipelines/templates/jobs/live.tests.yml b/eng/pipelines/templates/jobs/live.tests.yml
index 90575cda9782..5dd6d9d3c918 100644
--- a/eng/pipelines/templates/jobs/live.tests.yml
+++ b/eng/pipelines/templates/jobs/live.tests.yml
@@ -121,6 +121,9 @@ jobs:
- ${{ parameters.PreSteps }}
+ # Authenticate with Azure Artifacts
+ - template: /eng/pipelines/templates/steps/maven-authenticate.yml
+
- template: /eng/pipelines/templates/steps/build-and-test.yml
parameters:
PreTestRunSteps: ${{ parameters.PreTestRunSteps }}
diff --git a/eng/pipelines/templates/steps/maven-authenticate.yml b/eng/pipelines/templates/steps/maven-authenticate.yml
new file mode 100644
index 000000000000..31e6a0a635b9
--- /dev/null
+++ b/eng/pipelines/templates/steps/maven-authenticate.yml
@@ -0,0 +1,14 @@
+steps:
+ # Copy mirror settings to default Maven location so all requests go through CFS
+ - pwsh: |
+ $m2Dir = if ($env:USERPROFILE) { "$env:USERPROFILE\.m2" } else { "$HOME/.m2" }
+ New-Item -ItemType Directory -Force -Path $m2Dir | Out-Null
+ Copy-Item -Path "$(Build.SourcesDirectory)/eng/settings.xml" -Destination "$m2Dir/settings.xml"
+ displayName: 'Setup Maven mirror settings'
+
+ # Authenticate with Azure Artifacts feeds
+ # MavenAuthenticate adds entries to ~/.m2/settings.xml matching mirror id 'azure-sdk-for-java'
+ - task: MavenAuthenticate@0
+ displayName: 'Maven Authenticate'
+ inputs:
+ artifactsFeeds: 'azure-sdk-for-java'
\ No newline at end of file
diff --git a/eng/pipelines/templates/variables/globals.yml b/eng/pipelines/templates/variables/globals.yml
index 674aaebc4750..cbd64a9a98d1 100644
--- a/eng/pipelines/templates/variables/globals.yml
+++ b/eng/pipelines/templates/variables/globals.yml
@@ -26,7 +26,7 @@ variables:
# See https://github.com/actions/virtual-environments/issues/1499 for more info about the wagon options
# If reports about Maven dependency downloads become more common investigate re-introducing "-Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false", or other iterations of the configurations.
WagonOptions: '-Dmaven.wagon.httpconnectionManager.ttlSeconds=60 -Dmaven.wagon.http.pool=false'
- DefaultOptions: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER) --batch-mode --fail-at-end --settings eng/settings.xml $(WagonOptions)'
+ DefaultOptions: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER) --batch-mode --fail-at-end $(WagonOptions)'
LoggingOptions: '-Dorg.slf4j.simpleLogger.defaultLogLevel=$(MavenLogLevel) -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn'
MemoryOptions: '-Xmx4096m'
DefaultSkipOptions: '-Dgpg.skip -Dmaven.javadoc.skip=true -Dcodesnippet.skip=true -Dspotbugs.skip=true -Dcheckstyle.skip=true -Drevapi.skip=true -DtrimStackTrace=false -Dspotless.apply.skip=true -Dspotless.check.skip=true'
diff --git a/eng/settings.xml b/eng/settings.xml
index b1b7cb0d1d0d..47e8a88ef3b8 100644
--- a/eng/settings.xml
+++ b/eng/settings.xml
@@ -1,4 +1,13 @@
-
+
+
+
+ azure-sdk-for-java
+ Azure Artifacts Maven Mirror
+ https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1
+ external:*
+
+
diff --git a/eng/versioning/external_dependencies.txt b/eng/versioning/external_dependencies.txt
index ee3006837816..3e668c15d407 100644
--- a/eng/versioning/external_dependencies.txt
+++ b/eng/versioning/external_dependencies.txt
@@ -286,7 +286,6 @@ cosmos_org.apache.kafka:connect-runtime;3.6.0
cosmos_org.testcontainers:testcontainers;1.19.5
cosmos_org.testcontainers:kafka;1.19.5
cosmos_org.sourcelab:kafka-connect-client;4.0.4
-cosmos_io.confluent:kafka-avro-serializer;7.6.0
cosmos_org.apache.avro:avro;1.11.4
# Maven Tools for Cosmos Kafka connector only
diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt
index 552488a5703b..50e555b1d43d 100644
--- a/eng/versioning/version_client.txt
+++ b/eng/versioning/version_client.txt
@@ -104,7 +104,7 @@ com.azure:azure-core-test;1.27.0-beta.13;1.27.0-beta.14
com.azure:azure-core-tracing-opentelemetry;1.0.0-beta.61;1.0.0-beta.62
com.azure:azure-core-tracing-opentelemetry-samples;1.0.0-beta.1;1.0.0-beta.1
com.azure:azure-core-version-tests;1.0.0-beta.1;1.0.0-beta.1
-com.azure:azure-cosmos;4.75.0;4.76.0
+com.azure:azure-cosmos;4.75.0;4.76.1-hotfix
com.azure:azure-cosmos-benchmark;4.0.1-beta.1;4.0.1-beta.1
com.azure.cosmos.spark:azure-cosmos-spark_3;0.0.1-beta.1;0.0.1-beta.1
com.azure.cosmos.spark:azure-cosmos-spark_3-5;0.0.1-beta.1;0.0.1-beta.1
diff --git a/sdk/cosmos/azure-cosmos-benchmark/pom.xml b/sdk/cosmos/azure-cosmos-benchmark/pom.xml
index ee6a3ab1f2b1..ac0485ab90f8 100644
--- a/sdk/cosmos/azure-cosmos-benchmark/pom.xml
+++ b/sdk/cosmos/azure-cosmos-benchmark/pom.xml
@@ -52,7 +52,7 @@ Licensed under the MIT License.
com.azure
azure-cosmos
- 4.76.0
+ 4.76.1-hotfix
diff --git a/sdk/cosmos/azure-cosmos-encryption/pom.xml b/sdk/cosmos/azure-cosmos-encryption/pom.xml
index 2ec6e7beb33e..a6b4c32243c0 100644
--- a/sdk/cosmos/azure-cosmos-encryption/pom.xml
+++ b/sdk/cosmos/azure-cosmos-encryption/pom.xml
@@ -61,7 +61,7 @@ Licensed under the MIT License.
com.azure
azure-cosmos
- 4.76.0
+ 4.76.1-hotfix
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/pom.xml b/sdk/cosmos/azure-cosmos-kafka-connect/pom.xml
index 5590ebb9ff63..2ff4d5a1aa2c 100644
--- a/sdk/cosmos/azure-cosmos-kafka-connect/pom.xml
+++ b/sdk/cosmos/azure-cosmos-kafka-connect/pom.xml
@@ -27,11 +27,6 @@ Licensed under the MIT License.
-
- confluent
- Confluent
- https://packages.confluent.io/maven/
-
maven-repo1
Maven Repo1
@@ -92,7 +87,7 @@ Licensed under the MIT License.
com.azure
azure-cosmos
- 4.76.0
+ 4.76.1-hotfix
test
-
-
- io.confluent
- kafka-avro-serializer
- 7.6.0
- test
-
org.apache.avro
avro
@@ -419,7 +407,6 @@ Licensed under the MIT License.
org.slf4j
- io.confluent:*
org.apache.kafka:*
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/CosmosSinkConnectorITest.java b/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/CosmosSinkConnectorITest.java
index 52f6c0cca7ab..a4a7d42399d9 100644
--- a/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/CosmosSinkConnectorITest.java
+++ b/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/CosmosSinkConnectorITest.java
@@ -12,7 +12,6 @@
import com.azure.cosmos.kafka.connect.implementation.sink.IdStrategyType;
import com.azure.cosmos.models.PartitionKey;
import com.fasterxml.jackson.databind.JsonNode;
-import io.confluent.kafka.serializers.KafkaAvroSerializer;
import org.apache.avro.generic.GenericRecord;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
@@ -286,8 +285,8 @@ public void postAvroMessage() throws InterruptedException {
kafkaCosmosConnectContainer.registerConnector(connectorName, sinkConnectorConfig);
Properties producerProperties = kafkaCosmosConnectContainer.getProducerProperties();
- producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
- producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
+ producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, TestAvroSerializer.class.getName());
+ producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, TestAvroSerializer.class.getName());
KafkaProducer kafkaProducer = new KafkaProducer<>(producerProperties);
// first create few records in the topic
@@ -364,8 +363,8 @@ public void postAvroMessageWithTemplateIdStrategy() throws InterruptedException
kafkaCosmosConnectContainer.registerConnector(connectorName, sinkConnectorConfig);
Properties producerProperties = kafkaCosmosConnectContainer.getProducerProperties();
- producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
- producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
+ producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, TestAvroSerializer.class.getName());
+ producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, TestAvroSerializer.class.getName());
KafkaProducer kafkaProducer = new KafkaProducer<>(producerProperties);
logger.info("Creating sink record...");
@@ -432,8 +431,8 @@ public void postAvroMessageWithJsonPathInProvidedInKeyStrategy() throws Interrup
kafkaCosmosConnectContainer.registerConnector(connectorName, sinkConnectorConfig);
Properties producerProperties = kafkaCosmosConnectContainer.getProducerProperties();
- producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
- producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
+ producerProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, TestAvroSerializer.class.getName());
+ producerProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, TestAvroSerializer.class.getName());
KafkaProducer kafkaProducer = new KafkaProducer<>(producerProperties);
logger.info("Creating sink record...");
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/KafkaCosmosConnectContainer.java b/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/KafkaCosmosConnectContainer.java
index 43802acb5728..0345f4ba5dc9 100644
--- a/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/KafkaCosmosConnectContainer.java
+++ b/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/KafkaCosmosConnectContainer.java
@@ -3,10 +3,11 @@
package com.azure.cosmos.kafka.connect;
-import com.azure.core.exception.ResourceNotFoundException;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.common.errors.TopicExistsException;
+import org.apache.kafka.common.errors.UnknownTopicOrPartitionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sourcelab.kafka.connect.apiclient.Configuration;
@@ -14,18 +15,29 @@
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorDefinition;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorStatus;
import org.sourcelab.kafka.connect.apiclient.request.dto.NewConnectorDefinition;
+import org.sourcelab.kafka.connect.apiclient.rest.exceptions.InvalidRequestException;
+import org.sourcelab.kafka.connect.apiclient.rest.exceptions.ResourceNotFoundException;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.KafkaContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;
+import java.time.Duration;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
public class KafkaCosmosConnectContainer extends GenericContainer {
private static final Logger logger = LoggerFactory.getLogger(KafkaCosmosConnectContainer.class);
private static final int KAFKA_CONNECT_PORT = 8083;
+ private static final Duration KAFKA_CONNECT_REST_OPERATION_TIMEOUT = Duration.ofMinutes(2);
+ private static final Duration KAFKA_CONNECT_REST_RETRY_DELAY = Duration.ofMillis(500);
+ private static final int KAFKA_ADMIN_OPERATION_TIMEOUT_IN_SECONDS = 30;
private Properties producerProperties;
private Properties consumerProperties;
private AdminClient adminClient;
@@ -54,6 +66,10 @@ private void defaultConfig() {
// withEnv("CONNECT_LOG4J_LOGGERS", "org.apache.kafka=DEBUG,org.reflections=DEBUG,com.azure.cosmos.kafka=DEBUG");
withExposedPorts(KAFKA_CONNECT_PORT);
+ waitingFor(Wait.forHttp("/connectors")
+ .forPort(KAFKA_CONNECT_PORT)
+ .forStatusCode(200)
+ .withStartupTimeout(KAFKA_CONNECT_REST_OPERATION_TIMEOUT));
}
private Properties defaultConsumerConfig() {
@@ -158,13 +174,9 @@ public void registerConnector(String name, Map config) {
KafkaConnectClient kafkaConnectClient = new KafkaConnectClient(new Configuration(getTarget()));
logger.info("adding kafka connector {}", name);
-
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- ConnectorDefinition connectorDefinition = kafkaConnectClient.addConnector(newConnectorDefinition);
+ ConnectorDefinition connectorDefinition = executeWithKafkaConnectRestRetry(
+ "adding kafka connector " + name,
+ () -> kafkaConnectClient.addConnector(newConnectorDefinition));
logger.info("adding kafka connector completed with " + connectorDefinition);
}
@@ -212,7 +224,9 @@ public void resumeConnector(String name) {
public ConnectorStatus getConnectorStatus(String name) {
KafkaConnectClient kafkaConnectClient = new KafkaConnectClient(new Configuration(getTarget()));
- return kafkaConnectClient.getConnectorStatus(name);
+ return executeWithKafkaConnectRestRetry(
+ "getting kafka connector status " + name,
+ () -> kafkaConnectClient.getConnectorStatus(name));
}
public String getTarget() {
@@ -232,11 +246,91 @@ public Properties getConsumerProperties() {
}
public void createTopic(String topicName, int numPartitions) {
- this.adminClient.createTopics(
- Arrays.asList(new NewTopic(topicName, numPartitions, (short) replicationFactor)));
+ try {
+ this.adminClient.createTopics(
+ Arrays.asList(new NewTopic(topicName, numPartitions, (short) replicationFactor)))
+ .all()
+ .get(KAFKA_ADMIN_OPERATION_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ logger.info("Creating topic {} succeeded.", topicName);
+ } catch (ExecutionException exception) {
+ if (exception.getCause() instanceof TopicExistsException) {
+ logger.info("Topic {} already exists.", topicName);
+ return;
+ }
+
+ throw new RuntimeException("Failed to create topic " + topicName, exception);
+ } catch (InterruptedException exception) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while creating topic " + topicName, exception);
+ } catch (TimeoutException exception) {
+ throw new RuntimeException("Timed out while creating topic " + topicName, exception);
+ }
}
public void deleteTopic(String topicName) {
- this.adminClient.deleteTopics(Arrays.asList(topicName));
+ try {
+ this.adminClient.deleteTopics(Arrays.asList(topicName))
+ .all()
+ .get(KAFKA_ADMIN_OPERATION_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ logger.info("Deleting topic {} succeeded.", topicName);
+ } catch (ExecutionException exception) {
+ if (exception.getCause() instanceof UnknownTopicOrPartitionException) {
+ logger.info("Topic {} not found.", topicName);
+ return;
+ }
+
+ logger.warn("Failed to delete topic {}", topicName, exception);
+ } catch (InterruptedException exception) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while deleting topic " + topicName, exception);
+ } catch (TimeoutException exception) {
+ logger.warn("Timed out while deleting topic {}", topicName, exception);
+ }
+ }
+
+ private T executeWithKafkaConnectRestRetry(String operationName, Callable operation) {
+ long deadlineNanos = System.nanoTime() + KAFKA_CONNECT_REST_OPERATION_TIMEOUT.toNanos();
+ int attempts = 0;
+ InvalidRequestException lastException = null;
+
+ while (System.nanoTime() < deadlineNanos) {
+ attempts++;
+ try {
+ return operation.call();
+ } catch (InvalidRequestException exception) {
+ if (!isTransientKafkaConnectRestNotFound(exception)) {
+ throw exception;
+ }
+
+ lastException = exception;
+ logger.warn(
+ "Kafka Connect REST returned transient Not Found while {} on attempt {}. Retrying.",
+ operationName,
+ attempts,
+ exception);
+ } catch (Exception exception) {
+ throw new RuntimeException("Failed while " + operationName, exception);
+ }
+
+ sleepBeforeKafkaConnectRestRetry(operationName);
+ }
+
+ throw new RuntimeException(
+ "Timed out after " + KAFKA_CONNECT_REST_OPERATION_TIMEOUT.getSeconds()
+ + " seconds while " + operationName,
+ lastException);
+ }
+
+ private static boolean isTransientKafkaConnectRestNotFound(InvalidRequestException exception) {
+ return exception.getErrorCode() == 404;
+ }
+
+ private static void sleepBeforeKafkaConnectRestRetry(String operationName) {
+ try {
+ TimeUnit.MILLISECONDS.sleep(KAFKA_CONNECT_REST_RETRY_DELAY.toMillis());
+ } catch (InterruptedException exception) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while " + operationName, exception);
+ }
}
}
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/KafkaCosmosTestSuiteBase.java b/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/KafkaCosmosTestSuiteBase.java
index 56d51facc78c..d7a06012f948 100644
--- a/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/KafkaCosmosTestSuiteBase.java
+++ b/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/KafkaCosmosTestSuiteBase.java
@@ -19,6 +19,8 @@
import com.azure.cosmos.models.IncludedPath;
import com.azure.cosmos.models.IndexingPolicy;
import com.azure.cosmos.models.PartitionKeyDefinition;
+import com.azure.cosmos.models.SqlParameter;
+import com.azure.cosmos.models.SqlQuerySpec;
import com.azure.cosmos.models.ThroughputProperties;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.lang3.StringUtils;
@@ -35,9 +37,11 @@
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
+import java.util.concurrent.TimeUnit;
@Listeners({KafkaCosmosTestNGLogListener.class})
public class KafkaCosmosTestSuiteBase implements ITest {
@@ -46,6 +50,9 @@ public class KafkaCosmosTestSuiteBase implements ITest {
protected static final int SUITE_SETUP_TIMEOUT = 120000;
protected static final int SUITE_SHUTDOWN_TIMEOUT = 60000;
+ private static final int KAFKA_COSMOS_SUITE_SETUP_TIMEOUT = 10 * SUITE_SETUP_TIMEOUT;
+ private static final Duration CONTAINER_METADATA_MAX_WAIT = Duration.ofMinutes(2);
+ private static final Duration CONTAINER_METADATA_ATTEMPT_TIMEOUT = Duration.ofSeconds(10);
protected static final AzureKeyCredential credential;
protected static String databaseName;
@@ -89,7 +96,7 @@ protected static CosmosContainerProperties getSinglePartitionContainer(CosmosAsy
credential = new AzureKeyCredential(KafkaCosmosTestConfigurations.MASTER_KEY);
}
- @BeforeSuite(groups = { "kafka", "kafka-integration" }, timeOut = SUITE_SETUP_TIMEOUT)
+ @BeforeSuite(groups = { "kafka", "kafka-integration" }, timeOut = KAFKA_COSMOS_SUITE_SETUP_TIMEOUT)
public void beforeSuite() {
logger.info("beforeSuite Started");
@@ -119,9 +126,11 @@ public void beforeSuite() {
options,
6000);
}
+
+ waitForCreatedContainersToBeQueryable();
}
- @BeforeSuite(groups = { "kafka-emulator" }, timeOut = SUITE_SETUP_TIMEOUT)
+ @BeforeSuite(groups = { "kafka-emulator" }, timeOut = KAFKA_COSMOS_SUITE_SETUP_TIMEOUT)
public void beforeSuite_emulator() {
logger.info("beforeSuite Started");
@@ -151,6 +160,8 @@ public void beforeSuite_emulator() {
options,
6000);
}
+
+ waitForCreatedContainersToBeQueryable();
}
@BeforeSuite(groups = { "unit" }, timeOut = SUITE_SETUP_TIMEOUT)
@@ -227,6 +238,98 @@ private static String createCollection(
return cosmosContainerProperties.getId();
}
+ private static void waitForCreatedContainersToBeQueryable() {
+ try (CosmosAsyncClient probeClient = createGatewayHouseKeepingDocumentClient(true).buildAsyncClient()) {
+ waitForCreatedContainersToBeQueryable(
+ probeClient,
+ databaseName,
+ Arrays.asList(
+ multiPartitionContainerName,
+ multiPartitionContainerWithIdAsPartitionKeyName,
+ singlePartitionContainerName));
+ }
+ }
+
+ private static void waitForCreatedContainersToBeQueryable(
+ CosmosAsyncClient cosmosAsyncClient,
+ String databaseName,
+ List expectedContainerNames) {
+
+ long deadlineNanos = System.nanoTime() + CONTAINER_METADATA_MAX_WAIT.toNanos();
+ int attempts = 0;
+ Throwable lastFailure = null;
+
+ while (System.nanoTime() < deadlineNanos) {
+ attempts++;
+ try {
+ List visibleContainerNames = getVisibleContainerNames(
+ cosmosAsyncClient,
+ databaseName,
+ expectedContainerNames);
+
+ if (visibleContainerNames.containsAll(expectedContainerNames)) {
+ logger.info(
+ "Kafka test containers {} became queryable in database {} after {} attempt(s).",
+ expectedContainerNames,
+ databaseName,
+ attempts);
+ return;
+ }
+
+ lastFailure = new AssertionError(
+ "Expected containers " + expectedContainerNames + " but only found " + visibleContainerNames);
+ } catch (Exception exception) {
+ lastFailure = exception;
+ }
+
+ try {
+ TimeUnit.MILLISECONDS.sleep(500);
+ } catch (InterruptedException exception) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while waiting for Kafka test containers to become queryable.", exception);
+ }
+ }
+
+ throw new AssertionError(
+ "Kafka test containers " + expectedContainerNames + " were not queryable in database "
+ + databaseName + " within " + CONTAINER_METADATA_MAX_WAIT.getSeconds() + " seconds after "
+ + attempts + " attempt(s).",
+ lastFailure);
+ }
+
+ private static List getVisibleContainerNames(
+ CosmosAsyncClient cosmosAsyncClient,
+ String databaseName,
+ List expectedContainerNames) {
+
+ StringBuilder queryBuilder = new StringBuilder("SELECT * FROM c WHERE c.id IN (");
+ List parameters = new ArrayList<>();
+ for (int index = 0; index < expectedContainerNames.size(); index++) {
+ String parameterName = "@container" + index;
+ parameters.add(new SqlParameter(parameterName, expectedContainerNames.get(index)));
+ queryBuilder.append(parameterName);
+ if (index < expectedContainerNames.size() - 1) {
+ queryBuilder.append(", ");
+ }
+ }
+ queryBuilder.append(")");
+
+ List visibleContainers = cosmosAsyncClient
+ .getDatabase(databaseName)
+ .queryContainers(new SqlQuerySpec(queryBuilder.toString(), parameters))
+ .byPage()
+ .flatMapIterable(response -> response.getResults())
+ .collectList()
+ .block(CONTAINER_METADATA_ATTEMPT_TIMEOUT);
+
+ List visibleContainerNames = new ArrayList<>();
+ for (CosmosContainerProperties visibleContainer : visibleContainers) {
+ visibleContainerNames.add(visibleContainer.getId());
+ }
+
+ return visibleContainerNames;
+ }
+
static protected CosmosContainerProperties getCollectionDefinitionWithRangeRangeIndex(boolean enableAllVersionsAndDeletesPolicy) {
return getCollectionDefinitionWithRangeRangeIndex(Collections.singletonList("/mypk"), enableAllVersionsAndDeletesPolicy);
}
diff --git a/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/TestAvroSerializer.java b/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/TestAvroSerializer.java
new file mode 100644
index 000000000000..eb71d468ae63
--- /dev/null
+++ b/sdk/cosmos/azure-cosmos-kafka-connect/src/test/java/com/azure/cosmos/kafka/connect/TestAvroSerializer.java
@@ -0,0 +1,169 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.cosmos.kafka.connect;
+
+import org.apache.avro.generic.GenericDatumWriter;
+import org.apache.avro.generic.GenericRecord;
+import org.apache.avro.io.BinaryEncoder;
+import org.apache.avro.io.EncoderFactory;
+import org.apache.kafka.common.serialization.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A lightweight Avro serializer for integration tests that produces the Confluent wire format
+ * (magic byte 0x0 + 4-byte schema ID + Avro binary data) without requiring the
+ * io.confluent:kafka-avro-serializer dependency.
+ *
+ * This serializer registers schemas with a Schema Registry via its REST API and caches
+ * the resulting schema IDs.
+ */
+public class TestAvroSerializer implements Serializer {
+ private static final Logger LOGGER = LoggerFactory.getLogger(TestAvroSerializer.class);
+ private static final byte MAGIC_BYTE = 0x0;
+
+ private String schemaRegistryUrl;
+ private String basicAuthHeader;
+ private boolean isKey;
+ private final Map schemaIdCache = new ConcurrentHashMap<>();
+
+ @Override
+ public void configure(Map configs, boolean isKey) {
+ this.isKey = isKey;
+ this.schemaRegistryUrl = (String) configs.get("schema.registry.url");
+ if (this.schemaRegistryUrl != null && this.schemaRegistryUrl.endsWith("/")) {
+ this.schemaRegistryUrl = this.schemaRegistryUrl.substring(0, this.schemaRegistryUrl.length() - 1);
+ }
+
+ String authSource = (String) configs.get("basic.auth.credentials.source");
+ if ("USER_INFO".equals(authSource)) {
+ String userInfo = (String) configs.get("basic.auth.user.info");
+ if (userInfo != null) {
+ this.basicAuthHeader =
+ "Basic " + Base64.getEncoder().encodeToString(userInfo.getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ }
+
+ @Override
+ public byte[] serialize(String topic, GenericRecord record) {
+ if (record == null) {
+ return null;
+ }
+
+ try {
+ String subject = topic + (isKey ? "-key" : "-value");
+ int schemaId = getOrRegisterSchemaId(subject, record.getSchema().toString());
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ out.write(MAGIC_BYTE);
+ out.write(ByteBuffer.allocate(4).putInt(schemaId).array());
+
+ GenericDatumWriter writer = new GenericDatumWriter<>(record.getSchema());
+ BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(out, null);
+ writer.write(record, encoder);
+ encoder.flush();
+
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to serialize Avro record", e);
+ }
+ }
+
+ @Override
+ public void close() {
+ // No resources to close
+ }
+
+ private int getOrRegisterSchemaId(String subject, String schemaJson) {
+ String cacheKey = subject + ":" + schemaJson;
+ Integer cachedId = schemaIdCache.get(cacheKey);
+ if (cachedId != null) {
+ return cachedId;
+ }
+
+ try {
+ int id = registerSchema(subject, schemaJson);
+ schemaIdCache.put(cacheKey, id);
+ return id;
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to register schema for subject " + subject, e);
+ }
+ }
+
+ private int registerSchema(String subject, String schemaJson) throws IOException {
+ String url = schemaRegistryUrl + "/subjects/" + subject + "/versions";
+ LOGGER.info("Registering schema for subject {} at {}", subject, url);
+
+ String escapedSchema = schemaJson.replace("\\", "\\\\").replace("\"", "\\\"");
+ String requestBody = "{\"schema\": \"" + escapedSchema + "\"}";
+
+ HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
+ try {
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "application/vnd.schemaregistry.v1+json");
+ conn.setRequestProperty("Accept", "application/vnd.schemaregistry.v1+json");
+ if (basicAuthHeader != null) {
+ conn.setRequestProperty("Authorization", basicAuthHeader);
+ }
+ conn.setDoOutput(true);
+
+ try (OutputStream os = conn.getOutputStream()) {
+ os.write(requestBody.getBytes(StandardCharsets.UTF_8));
+ }
+
+ int responseCode = conn.getResponseCode();
+ if (responseCode != 200) {
+ String errorBody = readStream(conn.getErrorStream());
+ throw new IOException(
+ "Schema registration failed with HTTP " + responseCode + ": " + errorBody);
+ }
+
+ String response = readStream(conn.getInputStream());
+ return parseSchemaId(response);
+ } finally {
+ conn.disconnect();
+ }
+ }
+
+ private static String readStream(InputStream stream) {
+ if (stream == null) {
+ return "";
+ }
+ try (Scanner scanner = new Scanner(stream, "UTF-8")) {
+ return scanner.useDelimiter("\\A").hasNext() ? scanner.next() : "";
+ }
+ }
+
+ private static int parseSchemaId(String response) throws IOException {
+ // Response format: {"id": N} - parse the id value
+ int idIndex = response.indexOf("\"id\"");
+ if (idIndex == -1) {
+ throw new IOException("No schema ID found in response: " + response);
+ }
+ int colonPos = response.indexOf(':', idIndex);
+ int commaPos = response.indexOf(',', colonPos);
+ int bracePos = response.indexOf('}', colonPos);
+ int endPos;
+ if (commaPos == -1) {
+ endPos = bracePos;
+ } else {
+ endPos = Math.min(commaPos, bracePos);
+ }
+ return Integer.parseInt(response.substring(colonPos + 1, endPos).trim());
+ }
+}
\ No newline at end of file
diff --git a/sdk/cosmos/azure-cosmos-spark_3/pom.xml b/sdk/cosmos/azure-cosmos-spark_3/pom.xml
index 612967d22177..75d472bf1ec5 100644
--- a/sdk/cosmos/azure-cosmos-spark_3/pom.xml
+++ b/sdk/cosmos/azure-cosmos-spark_3/pom.xml
@@ -58,7 +58,7 @@
com.azure
azure-cosmos
- 4.76.0
+ 4.76.1-hotfix
org.slf4j
diff --git a/sdk/cosmos/azure-cosmos-test/pom.xml b/sdk/cosmos/azure-cosmos-test/pom.xml
index db566dd3bdc2..1d310ced4e51 100644
--- a/sdk/cosmos/azure-cosmos-test/pom.xml
+++ b/sdk/cosmos/azure-cosmos-test/pom.xml
@@ -59,7 +59,7 @@ Licensed under the MIT License.
com.azure
azure-cosmos
- 4.76.0
+ 4.76.1-hotfix
diff --git a/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/faultinjection/FaultInjectionRuleBuilder.java b/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/faultinjection/FaultInjectionRuleBuilder.java
index 303d5ee93328..c6e88d676a98 100644
--- a/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/faultinjection/FaultInjectionRuleBuilder.java
+++ b/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/faultinjection/FaultInjectionRuleBuilder.java
@@ -164,17 +164,27 @@ private void validateRuleOnGatewayConnection() {
throw new IllegalArgumentException("STALED_ADDRESSES exception can not be injected for rule with gateway connection type");
}
- // for metadata request related rule, only CONNECTION_DELAY, RESPONSE_DELAY, TOO_MANY_REQUEST error can be injected
+ // for metadata request related rule, only metadata-safe errors can be injected
if (ImplementationBridgeHelpers
.FaultInjectionConditionHelper
.getFaultInjectionConditionAccessor()
.isMetadataOperationType(this.condition)) {
- if (serverErrorResult.getServerErrorType() != FaultInjectionServerErrorType.TOO_MANY_REQUEST
- && serverErrorResult.getServerErrorType() != FaultInjectionServerErrorType.RESPONSE_DELAY
- && serverErrorResult.getServerErrorType() != FaultInjectionServerErrorType.CONNECTION_DELAY) {
+ if (!isSupportedMetadataServerErrorType(serverErrorResult.getServerErrorType())) {
throw new IllegalArgumentException("Error type " + serverErrorResult.getServerErrorType() + " is not supported for rule with metadata request");
}
}
}
+
+ private boolean isSupportedMetadataServerErrorType(FaultInjectionServerErrorType serverErrorType) {
+ if (serverErrorType == FaultInjectionServerErrorType.TOO_MANY_REQUEST
+ || serverErrorType == FaultInjectionServerErrorType.RESPONSE_DELAY
+ || serverErrorType == FaultInjectionServerErrorType.CONNECTION_DELAY) {
+ return true;
+ }
+
+ return this.condition.getOperationType() == FaultInjectionOperationType.METADATA_REQUEST_PARTITION_KEY_RANGES
+ && (serverErrorType == FaultInjectionServerErrorType.OWNER_RESOURCE_NOT_EXISTS
+ || serverErrorType == FaultInjectionServerErrorType.COLLECTION_NOT_AVAILABLE_FOR_READ);
+ }
}
diff --git a/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/faultinjection/FaultInjectionServerErrorType.java b/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/faultinjection/FaultInjectionServerErrorType.java
index bd563cb8e6fd..a06b3d2bfd32 100644
--- a/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/faultinjection/FaultInjectionServerErrorType.java
+++ b/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/faultinjection/FaultInjectionServerErrorType.java
@@ -23,6 +23,12 @@ public enum FaultInjectionServerErrorType {
/** 404-1002 from server */
READ_SESSION_NOT_AVAILABLE,
+ /** 404-1003 from server */
+ OWNER_RESOURCE_NOT_EXISTS,
+
+ /** 404-1013 from server */
+ COLLECTION_NOT_AVAILABLE_FOR_READ,
+
/** 408 from server */
TIMEOUT,
diff --git a/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/implementation/faultinjection/FaultInjectionServerErrorResultInternal.java b/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/implementation/faultinjection/FaultInjectionServerErrorResultInternal.java
index da70fe9444e5..dccf9be04538 100644
--- a/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/implementation/faultinjection/FaultInjectionServerErrorResultInternal.java
+++ b/sdk/cosmos/azure-cosmos-test/src/main/java/com/azure/cosmos/test/implementation/faultinjection/FaultInjectionServerErrorResultInternal.java
@@ -134,6 +134,18 @@ public CosmosException getInjectedServerError(RxDocumentServiceRequest request)
cosmosException = new NotFoundException(null, lsn, partitionKeyRangeId, responseHeaders);
break;
+ case OWNER_RESOURCE_NOT_EXISTS:
+ responseHeaders.put(WFConstants.BackendHeaders.SUB_STATUS,
+ Integer.toString(HttpConstants.SubStatusCodes.OWNER_RESOURCE_NOT_EXISTS));
+ cosmosException = new NotFoundException(null, lsn, partitionKeyRangeId, responseHeaders);
+ break;
+
+ case COLLECTION_NOT_AVAILABLE_FOR_READ:
+ responseHeaders.put(WFConstants.BackendHeaders.SUB_STATUS,
+ Integer.toString(1013));
+ cosmosException = new NotFoundException(null, lsn, partitionKeyRangeId, responseHeaders);
+ break;
+
case PARTITION_IS_MIGRATING:
responseHeaders.put(WFConstants.BackendHeaders.SUB_STATUS,
Integer.toString(HttpConstants.SubStatusCodes.COMPLETING_PARTITION_MIGRATION));
diff --git a/sdk/cosmos/azure-cosmos-tests/pom.xml b/sdk/cosmos/azure-cosmos-tests/pom.xml
index cf7a2c8cb5e3..8ddba3d83ba2 100644
--- a/sdk/cosmos/azure-cosmos-tests/pom.xml
+++ b/sdk/cosmos/azure-cosmos-tests/pom.xml
@@ -100,7 +100,7 @@ Licensed under the MIT License.
com.azure
azure-cosmos
- 4.76.0
+ 4.76.1-hotfix
com.azure
@@ -665,6 +665,60 @@ Licensed under the MIT License.
+
+
+ fi-customer-workflows
+
+ fi-customer-workflows
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.5.3
+
+
+ src/test/resources/fi-customer-workflows-testng.xml
+
+
+ true
+ 1
+ 256
+ paranoid
+
+
+
+
+
+
+
+
+ fi-sm-customer-workflows
+
+ fi-sm-customer-workflows
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.5.3
+
+
+ src/test/resources/fi-sm-customer-workflows-testng.xml
+
+
+ true
+ 1
+ 256
+ paranoid
+
+
+
+
+
+
multi-region
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/AsyncCacheNonBlockingIntegrationTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/AsyncCacheNonBlockingIntegrationTest.java
index 9f68a0dd9143..94671917f884 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/AsyncCacheNonBlockingIntegrationTest.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/AsyncCacheNonBlockingIntegrationTest.java
@@ -14,7 +14,7 @@
import com.azure.cosmos.models.CosmosBulkOperationResponse;
import com.azure.cosmos.models.CosmosBulkOperations;
import com.azure.cosmos.models.CosmosContainerProperties;
-import com.azure.cosmos.models.CosmosContainerResponse;
+import com.azure.cosmos.models.CosmosContainerRequestOptions;
import com.azure.cosmos.models.CosmosItemOperation;
import com.azure.cosmos.models.CosmosQueryRequestOptions;
import com.azure.cosmos.models.FeedResponse;
@@ -68,8 +68,10 @@ public void createItem_withCacheRefresh() throws InterruptedException {
String containerId = "bulksplittestcontainer_" + UUID.randomUUID();
int totalRequest = getTotalRequest();
CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerId, "/mypk");
- CosmosContainerResponse containerResponse = createdDatabase.createContainer(containerProperties).block();
- CosmosAsyncContainer container = createdDatabase.getContainer(containerId);
+ CosmosAsyncContainer container = createCollection(
+ createdDatabase,
+ containerProperties,
+ new CosmosContainerRequestOptions());
Flux cosmosItemOperationFlux1 = Flux.range(0, totalRequest).map(i -> {
String partitionKey = UUID.randomUUID().toString();
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/AzureKeyCredentialTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/AzureKeyCredentialTest.java
index 9166fcc4ca9a..484fb9404b4e 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/AzureKeyCredentialTest.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/AzureKeyCredentialTest.java
@@ -103,8 +103,7 @@ public void readCollectionWithSecondaryKey(String collectionName) throws Interru
// sanity check
assertThat(client.credential().getKey()).isEqualTo(TestConfigurations.MASTER_KEY);
- database.createContainer(collectionDefinition).block();
- CosmosAsyncContainer collection = database.getContainer(collectionDefinition.getId());
+ CosmosAsyncContainer collection = createCollection(database, collectionDefinition, new CosmosContainerRequestOptions());
credential.update(TestConfigurations.SECONDARY_MASTER_KEY);
Mono readObservable = collection.read();
@@ -126,8 +125,7 @@ public void deleteCollectionWithSecondaryKey(String collectionName) throws Inter
// sanity check
assertThat(client.credential().getKey()).isEqualTo(TestConfigurations.MASTER_KEY);
- database.createContainer(collectionDefinition).block();
- CosmosAsyncContainer collection = database.getContainer(collectionDefinition.getId());
+ CosmosAsyncContainer collection = createCollection(database, collectionDefinition, new CosmosContainerRequestOptions());
credential.update(TestConfigurations.SECONDARY_MASTER_KEY);
Mono deleteObservable = collection.delete();
@@ -144,8 +142,7 @@ public void deleteCollectionWithSecondaryKey(String collectionName) throws Inter
public void replaceCollectionWithSecondaryKey(String collectionName) throws InterruptedException {
// create a collection
CosmosContainerProperties collectionDefinition = getCollectionDefinition(collectionName);
- database.createContainer(collectionDefinition).block();
- CosmosAsyncContainer collection = database.getContainer(collectionDefinition.getId());
+ CosmosAsyncContainer collection = createCollection(database, collectionDefinition, new CosmosContainerRequestOptions());
// sanity check
assertThat(client.credential().getKey()).isEqualTo(TestConfigurations.MASTER_KEY);
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/ChangeFeedContinuationTokenUtilsTests.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/ChangeFeedContinuationTokenUtilsTests.java
index cc0082e492d9..9203e8c726c3 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/ChangeFeedContinuationTokenUtilsTests.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/ChangeFeedContinuationTokenUtilsTests.java
@@ -59,7 +59,9 @@ public void extractContinuationTokens() {
CosmosAsyncContainer testContainer =
createCollection(this.createdDatabase, containerProperties, new CosmosContainerRequestOptions(), 18000);
- List feedRanges = testContainer.getFeedRanges().block();
+ List feedRanges = getFeedRangesWithRetry(
+ testContainer,
+ "get feed ranges for change feed continuation token test container");
assertThat(feedRanges.size()).isEqualTo(3);
// create few items into the container
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/ClientMetricsTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/ClientMetricsTest.java
index e4b52958c2de..388e8be819c7 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/ClientMetricsTest.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/ClientMetricsTest.java
@@ -25,7 +25,6 @@
import com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdEndpoint;
import com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdServiceEndpoint;
import com.azure.cosmos.implementation.guava25.collect.Lists;
-import com.azure.cosmos.implementation.routing.LocationCache;
import com.azure.cosmos.models.CosmosBatch;
import com.azure.cosmos.models.CosmosBatchResponse;
import com.azure.cosmos.models.CosmosBulkExecutionOptions;
@@ -60,7 +59,6 @@
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
-import java.lang.reflect.Field;
import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
@@ -85,7 +83,7 @@ public ClientMetricsTest(CosmosClientBuilder clientBuilder) {
super(clientBuilder);
}
- @Test(groups = { "fast" }, timeOut = TIMEOUT)
+ @Test(groups = { "fast" }, timeOut = SETUP_TIMEOUT, retryAnalyzer = FlakyTestRetryAnalyzer.class)
public void maxValueExceedingDefinedLimitStillWorksWithoutException() throws Exception {
// Expected behavior is that higher values than the expected max value can still be recorded
@@ -375,7 +373,8 @@ public void readManySingleItem() throws Exception {
}
}
- @Test(groups = { "fast" }, timeOut = TIMEOUT)
+ // TestState constructor creates a new client and collection, which can exceed 40s in CI.
+ @Test(groups = { "fast" }, timeOut = SETUP_TIMEOUT, retryAnalyzer = FlakyTestRetryAnalyzer.class)
public void readManyMultipleItems() throws Exception {
List createdDocs = new ArrayList<>();
List tuplesToBeRead = new ArrayList<>();
@@ -1451,7 +1450,6 @@ private static class TestState implements AutoCloseable {
private final String databaseId;
private final String containerId;
private final MeterRegistry meterRegistry;
- private String preferredRegion;
private final CosmosClientTelemetryConfig inputClientTelemetryConfig;
private final CosmosMicrometerMetricsOptions inputMetricsOptions;
private Tag clientCorrelationTag;
@@ -1501,13 +1499,6 @@ public TestState(CosmosClientBuilder clientBuilder,
.getMetricCategories(this.client.asyncClient())
).isSameAs(this.getEffectiveMetricCategories());
- AsyncDocumentClient asyncDocumentClient = ReflectionUtils.getAsyncDocumentClient(this.client.asyncClient());
- RxDocumentClientImpl rxDocumentClient = (RxDocumentClientImpl) asyncDocumentClient;
-
- List writeRegions = this.getAvailableWriteRegionNames(rxDocumentClient);
- assertThat(writeRegions).isNotNull().isNotEmpty();
- this.preferredRegion = writeRegions.iterator().next();
-
CosmosClient mgmtClient = clientBuilder
.clientTelemetryConfig(
new CosmosClientTelemetryConfig().metricsOptions(new CosmosMicrometerMetricsOptions().setEnabled(false))
@@ -1578,31 +1569,6 @@ public void close() throws Exception {
}
}
- private static List getAvailableWriteRegionNames(RxDocumentClientImpl rxDocumentClient) {
- try {
- GlobalEndpointManager globalEndpointManager = ReflectionUtils.getGlobalEndpointManager(rxDocumentClient);
- LocationCache locationCache = ReflectionUtils.getLocationCache(globalEndpointManager);
-
- Field locationInfoField = LocationCache.class.getDeclaredField("locationInfo");
- locationInfoField.setAccessible(true);
- Object locationInfo = locationInfoField.get(locationCache);
-
- Class> DatabaseAccountLocationsInfoClass = Class.forName("com.azure.cosmos.implementation.routing" +
- ".LocationCache$DatabaseAccountLocationsInfo");
- Field availableWriteLocations = DatabaseAccountLocationsInfoClass.getDeclaredField(
- "availableWriteLocations");
- availableWriteLocations.setAccessible(true);
- @SuppressWarnings("unchecked")
- List list = (List) availableWriteLocations.get(locationInfo);
- return list;
-
- } catch (Exception error) {
- fail(error.toString());
-
- return null;
- }
- }
-
public EnumSet getEffectiveMetricCategories() {
return ImplementationBridgeHelpers
.CosmosClientTelemetryConfigHelper
@@ -1716,11 +1682,7 @@ public void validateMetrics(Tag expectedOperationTag, Tag expectedRequestTag, in
if (this.getEffectiveMetricCategories().contains(MetricCategory.OperationDetails)) {
this.assertMetrics("cosmos.client.op.regionsContacted", true, expectedOperationTag);
-
- this.assertMetrics(
- "cosmos.client.op.regionsContacted",
- true,
- Tag.of(TagName.RegionName.toString(), this.preferredRegion.toLowerCase(Locale.ROOT)));
+ this.assertMetricsWithPopulatedRegionName("cosmos.client.op.regionsContacted");
}
if (this.getEffectiveMetricCategories().contains(MetricCategory.RequestSummary)) {
@@ -1737,10 +1699,7 @@ public void validateMetrics(Tag expectedOperationTag, Tag expectedRequestTag, in
if (this.client.asyncClient().getConnectionPolicy().getConnectionMode() == ConnectionMode.DIRECT) {
this.assertMetrics("cosmos.client.req.rntbd.latency", true, expectedRequestTag);
- this.assertMetrics(
- "cosmos.client.req.rntbd.latency",
- true,
- Tag.of(TagName.RegionName.toString(), this.preferredRegion.toLowerCase(Locale.ROOT)));
+ this.assertMetricsWithPopulatedRegionName("cosmos.client.req.rntbd.latency");
this.assertMetrics("cosmos.client.req.rntbd.backendLatency", true, expectedRequestTag);
this.assertMetrics("cosmos.client.req.rntbd.requests", true, expectedRequestTag);
Meter reportedRntbdRequestCharge =
@@ -1754,10 +1713,7 @@ public void validateMetrics(Tag expectedOperationTag, Tag expectedRequestTag, in
this.assertMetrics("cosmos.client.req.gw.latency", true, expectedRequestTag);
if (this.getEffectiveMetricCategories().contains(MetricCategory.OperationDetails)) {
- this.assertMetrics(
- "cosmos.client.req.gw.latency",
- true,
- Tag.of(TagName.RegionName.toString(), this.preferredRegion.toLowerCase(Locale.ROOT)));
+ this.assertMetricsWithPopulatedRegionName("cosmos.client.req.gw.latency");
}
this.assertMetrics("cosmos.client.req.gw.backendLatency", false, expectedRequestTag);
this.assertMetrics("cosmos.client.req.gw.requests", true, expectedRequestTag);
@@ -1775,6 +1731,43 @@ public Meter assertMetrics(String prefix, boolean expectedToFind) {
return assertMetrics(prefix, expectedToFind, null);
}
+ public Meter assertMetricsWithPopulatedRegionName(String prefix) {
+ assertThat(this.meterRegistry).isNotNull();
+ assertThat(this.meterRegistry.getMeters()).isNotNull();
+ List meters = this.meterRegistry.getMeters().stream().collect(Collectors.toList());
+ assertThat(meters.size()).isGreaterThan(0);
+ assertTagInAllMeters(meters, prefix);
+
+ List meterPrefixMatches = meters
+ .stream()
+ .filter(meter -> meter.getId().getName().startsWith(prefix))
+ .collect(Collectors.toList());
+
+ List meterMatches = meterPrefixMatches
+ .stream()
+ .filter(meter -> meter.getId().getTags().stream().anyMatch(tag ->
+ TagName.RegionName.toString().equals(tag.getKey())
+ && !"NONE".equalsIgnoreCase(tag.getValue())
+ && !tag.getValue().isEmpty())
+ && meter.measure().iterator().next().getValue() > 0)
+ .collect(Collectors.toList());
+
+ if (meterMatches.size() == 0) {
+ String message = String.format(
+ "No meter found for prefix '%s' with a populated RegionName tag",
+ prefix);
+
+ logger.error(message);
+ logger.info("Meters matching the prefix");
+ meterPrefixMatches.forEach(meter ->
+ logger.info("{} has measurements {}", meter.getId(), meter.measure().iterator().hasNext()));
+
+ fail(message);
+ }
+
+ return meterMatches.get(0);
+ }
+
public Meter assertMetrics(String prefix, boolean expectedToFind, Tag withTag) {
assertThat(this.meterRegistry).isNotNull();
assertThat(this.meterRegistry.getMeters()).isNotNull();
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkAsyncTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkAsyncTest.java
index e7d7ea67c0ce..c1379c478a76 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkAsyncTest.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkAsyncTest.java
@@ -70,12 +70,12 @@ public void afterClass() {
safeClose(this.bulkClient);
}
- @Test(groups = {"fast"}, timeOut = TIMEOUT * 2)
+ @Test(groups = {"fast"}, timeOut = 4 * SETUP_TIMEOUT, retryAnalyzer = FlakyTestRetryAnalyzer.class)
public void createItem_withBulkAndThroughputControlAsDefaultGroup() throws InterruptedException {
runBulkTest(true);
}
- @Test(groups = {"fast"}, timeOut = TIMEOUT * 2)
+ @Test(groups = {"fast"}, timeOut = 4 * SETUP_TIMEOUT, retryAnalyzer = FlakyTestRetryAnalyzer.class)
public void createItem_withBulkAndThroughputControlAsNonDefaultGroup() throws InterruptedException {
runBulkTest(false);
}
@@ -149,7 +149,7 @@ private void runBulkTest(boolean isDefaultTestGroup) throws InterruptedException
}
}
- @Test(groups = {"fast"}, timeOut = TIMEOUT)
+ @Test(groups = {"fast"}, timeOut = 2 * TIMEOUT, retryAnalyzer = SuperFlakyTestRetryAnalyzer.class)
public void createItem_withBulk() {
int totalRequest = getTotalRequest();
@@ -197,73 +197,82 @@ public void createItem_withBulk() {
assertThat(processedDoc.get()).isEqualTo(totalRequest * 2);
}
- @Test(groups = {"fast"}, timeOut = TIMEOUT)
+ @Test(groups = {"fast"}, timeOut = 2 * TIMEOUT, retryAnalyzer = SuperFlakyTestRetryAnalyzer.class)
public void createItem_withBulk_after_collectionRecreate() {
int totalRequest = getTotalRequest();
- for(int x = 0; x < 2; x = x + 1) {
- Flux cosmosItemOperationFlux = Flux.merge(
- Flux.range(0, totalRequest).map(i -> {
- String partitionKey = UUID.randomUUID().toString();
- TestDoc testDoc = this.populateTestDoc(partitionKey);
-
- return CosmosBulkOperations.getCreateItemOperation(testDoc, new PartitionKey(partitionKey));
- }),
- Flux.range(0, totalRequest).map(i -> {
- String partitionKey = UUID.randomUUID().toString();
- EventDoc eventDoc = new EventDoc(UUID.randomUUID().toString(), 2, 4, "type1", partitionKey);
-
- return CosmosBulkOperations.getCreateItemOperation(eventDoc, new PartitionKey(partitionKey));
- }));
-
- CosmosBulkExecutionOptions cosmosBulkExecutionOptions = new CosmosBulkExecutionOptions();
-
- Flux> responseFlux = bulkAsyncContainer
- .executeBulkOperations(cosmosItemOperationFlux, cosmosBulkExecutionOptions);
-
- AtomicInteger processedDoc = new AtomicInteger(0);
- responseFlux
- .flatMap((com.azure.cosmos.models.CosmosBulkOperationResponse cosmosBulkOperationResponse) -> {
-
- processedDoc.incrementAndGet();
+ // This test deletes and recreates the container it operates on. Use a dedicated container
+ // (never the suite-shared container) so the delete/recreate cannot leave the shared container
+ // in a not-ready state and cascade CollectionRoutingMapNotFound failures into other test classes.
+ // Readiness uses a single default-route probe instead of the multi-region warm-up - this test does not
+ // need multi-region readiness, and skipping the per-region probes keeps the repeated create/recreate
+ // cycle cheap enough to avoid a metadata-request throttling storm.
+ CosmosAsyncDatabase db = bulkAsyncContainer.getDatabase();
+ String containerName = UUID.randomUUID().toString();
+ db.createContainer(containerName, "/mypk", ThroughputProperties.createManualThroughput(10_100)).block();
+ CosmosAsyncContainer recreateContainer = db.getContainer(containerName);
+ waitForCollectionToBeReadableOnDefaultRoute(recreateContainer, this.bulkClient);
- com.azure.cosmos.models.CosmosBulkItemResponse cosmosBulkItemResponse = cosmosBulkOperationResponse.getResponse();
- if (cosmosBulkOperationResponse.getException() != null) {
- logger.error("Bulk operation failed", cosmosBulkOperationResponse.getException());
- fail(cosmosBulkOperationResponse.getException().toString());
- }
-
- assertThat(cosmosBulkItemResponse.getStatusCode()).isEqualTo(HttpResponseStatus.CREATED.code());
- assertThat(cosmosBulkItemResponse.getRequestCharge()).isGreaterThan(0);
- assertThat(cosmosBulkItemResponse.getCosmosDiagnostics().toString()).isNotNull();
- assertThat(cosmosBulkItemResponse.getSessionToken()).isNotNull();
- assertThat(cosmosBulkItemResponse.getActivityId()).isNotNull();
- assertThat(cosmosBulkItemResponse.getRequestCharge()).isNotNull();
-
- return Mono.just(cosmosBulkItemResponse);
- }).blockLast();
-
- assertThat(processedDoc.get()).isEqualTo(totalRequest * 2);
-
- CosmosAsyncDatabase db = bulkAsyncContainer
- .getDatabase();
- String containerName = bulkAsyncContainer.getId();
-
- // Manually deleting and recreating the container
- // on the same client (same async cache instances)
- // to validate correct mitigation after delete and recreate
- bulkAsyncContainer.delete().block();
- db
- .createContainer(
- containerName,
- "/mypk",
- ThroughputProperties.createManualThroughput(10_100))
- .block();
- bulkAsyncContainer = db.getContainer(containerName);
+ try {
+ for (int x = 0; x < 2; x = x + 1) {
+ Flux cosmosItemOperationFlux = Flux.merge(
+ Flux.range(0, totalRequest).map(i -> {
+ String partitionKey = UUID.randomUUID().toString();
+ TestDoc testDoc = this.populateTestDoc(partitionKey);
+
+ return CosmosBulkOperations.getCreateItemOperation(testDoc, new PartitionKey(partitionKey));
+ }),
+ Flux.range(0, totalRequest).map(i -> {
+ String partitionKey = UUID.randomUUID().toString();
+ EventDoc eventDoc = new EventDoc(UUID.randomUUID().toString(), 2, 4, "type1", partitionKey);
+
+ return CosmosBulkOperations.getCreateItemOperation(eventDoc, new PartitionKey(partitionKey));
+ }));
+
+ CosmosBulkExecutionOptions cosmosBulkExecutionOptions = new CosmosBulkExecutionOptions();
+
+ Flux> responseFlux = recreateContainer
+ .executeBulkOperations(cosmosItemOperationFlux, cosmosBulkExecutionOptions);
+
+ AtomicInteger processedDoc = new AtomicInteger(0);
+ responseFlux
+ .flatMap((com.azure.cosmos.models.CosmosBulkOperationResponse cosmosBulkOperationResponse) -> {
+
+ processedDoc.incrementAndGet();
+
+ com.azure.cosmos.models.CosmosBulkItemResponse cosmosBulkItemResponse = cosmosBulkOperationResponse.getResponse();
+ if (cosmosBulkOperationResponse.getException() != null) {
+ logger.error("Bulk operation failed", cosmosBulkOperationResponse.getException());
+ fail(cosmosBulkOperationResponse.getException().toString());
+ }
+
+ assertThat(cosmosBulkItemResponse.getStatusCode()).isEqualTo(HttpResponseStatus.CREATED.code());
+ assertThat(cosmosBulkItemResponse.getRequestCharge()).isGreaterThan(0);
+ assertThat(cosmosBulkItemResponse.getCosmosDiagnostics().toString()).isNotNull();
+ assertThat(cosmosBulkItemResponse.getSessionToken()).isNotNull();
+ assertThat(cosmosBulkItemResponse.getActivityId()).isNotNull();
+ assertThat(cosmosBulkItemResponse.getRequestCharge()).isNotNull();
+
+ return Mono.just(cosmosBulkItemResponse);
+ }).blockLast();
+
+ assertThat(processedDoc.get()).isEqualTo(totalRequest * 2);
+
+ // Manually deleting and recreating the container (same name) on the same client (same async
+ // cache instances) to validate correct cache mitigation after delete and recreate. The
+ // single default-route readiness probe bridges the post-recreate window in which the routing
+ // map is not yet available (the SDK now surfaces that quickly instead of retrying).
+ recreateContainer.delete().block();
+ db.createContainer(containerName, "/mypk", ThroughputProperties.createManualThroughput(10_100)).block();
+ recreateContainer = db.getContainer(containerName);
+ waitForCollectionToBeReadableOnDefaultRoute(recreateContainer, this.bulkClient);
+ }
+ } finally {
+ recreateContainer.delete().onErrorResume(t -> Mono.empty()).block();
}
}
- @Test(groups = {"fast"}, timeOut = TIMEOUT)
+ @Test(groups = {"fast"}, timeOut = 2 * TIMEOUT, retryAnalyzer = SuperFlakyTestRetryAnalyzer.class)
public void createItem_withBulk_and_operationLevelContext() {
int totalRequest = getTotalRequest();
@@ -325,7 +334,7 @@ public void createItem_withBulk_and_operationLevelContext() {
assertThat(processedDoc.get()).isEqualTo(totalRequest * 2);
}
- @Test(groups = {"fast"}, timeOut = TIMEOUT)
+ @Test(groups = {"fast"}, timeOut = 2 * TIMEOUT, retryAnalyzer = SuperFlakyTestRetryAnalyzer.class)
public void createItemMultipleTimesWithOperationOnFly_withBulk() {
int totalRequest = getTotalRequest();
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkGatewayTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkGatewayTest.java
index b33e9d9ac1bb..d0966b463135 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkGatewayTest.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkGatewayTest.java
@@ -9,7 +9,7 @@
import com.azure.cosmos.models.CosmosBulkOperationResponse;
import com.azure.cosmos.models.CosmosBulkOperations;
import com.azure.cosmos.models.CosmosContainerProperties;
-import com.azure.cosmos.models.CosmosContainerResponse;
+import com.azure.cosmos.models.CosmosContainerRequestOptions;
import com.azure.cosmos.models.CosmosQueryRequestOptions;
import com.azure.cosmos.models.FeedResponse;
import com.azure.cosmos.models.PartitionKey;
@@ -61,8 +61,10 @@ public void createItem_withBulk_split() throws InterruptedException {
String containerId = "bulksplittestcontainer_" + UUID.randomUUID();
int totalRequest = getTotalRequest();
CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerId, "/mypk");
- CosmosContainerResponse containerResponse = createdDatabase.createContainer(containerProperties).block();
- CosmosAsyncContainer container = createdDatabase.getContainer(containerId);
+ CosmosAsyncContainer container = createCollection(
+ createdDatabase,
+ containerProperties,
+ new CosmosContainerRequestOptions());
Flux cosmosItemOperationFlux1 = Flux.range(0, totalRequest).map(i -> {
String partitionKey = UUID.randomUUID().toString();
diff --git a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkTest.java b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkTest.java
index f8d332edc29a..79b898e4160c 100644
--- a/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkTest.java
+++ b/sdk/cosmos/azure-cosmos-tests/src/test/java/com/azure/cosmos/CosmosBulkTest.java
@@ -4,11 +4,14 @@
package com.azure.cosmos;
import com.azure.cosmos.implementation.ISessionToken;
+import com.azure.cosmos.implementation.DatabaseAccount;
+import com.azure.cosmos.implementation.DatabaseAccountLocation;
import com.azure.cosmos.implementation.guava25.base.Function;
import com.azure.cosmos.implementation.guava25.collect.Lists;
import com.azure.cosmos.models.CosmosBulkExecutionOptions;
import com.azure.cosmos.models.CosmosBulkItemRequestOptions;
import com.azure.cosmos.models.CosmosBulkOperations;
+import com.azure.cosmos.models.CosmosItemRequestOptions;
import com.azure.cosmos.models.CosmosItemResponse;
import com.azure.cosmos.models.PartitionKey;
import io.netty.handler.codec.http.HttpResponseStatus;
@@ -20,6 +23,7 @@
import org.testng.annotations.Test;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
@@ -531,9 +535,18 @@ private void runWithError(
public void bulkSessionTokenTest() {
this.createJsonTestDocs(bulkContainer);
+ String secondWriteRegion = getSecondWriteRegionName();
+ CosmosItemRequestOptions baselineReadOptions = new CosmosItemRequestOptions();
+ CosmosBulkExecutionOptions bulkExecutionOptions = new CosmosBulkExecutionOptions();
+ if (secondWriteRegion != null) {
+ baselineReadOptions.setExcludedRegions(Collections.singletonList(secondWriteRegion));
+ bulkExecutionOptions.setExcludedRegions(Collections.singletonList(secondWriteRegion));
+ }
+
CosmosItemResponse readResponse = bulkContainer.readItem(
this.TestDocPk1ExistingC.getId(),
this.getPartitionKey(this.partitionKey1),
+ baselineReadOptions,
TestDoc.class);
assertThat(readResponse.getStatusCode()).isEqualTo(HttpResponseStatus.OK.code());
@@ -555,25 +568,56 @@ public void bulkSessionTokenTest() {
operations.add(
CosmosBulkOperations.getDeleteItemOperation(this.TestDocPk1ExistingC.getId(), new PartitionKey(this.partitionKey1)));
- List> bulkResponses = Lists.newArrayList(bulkContainer.executeBulkOperations(operations));
+ List> bulkResponses = Lists.newArrayList(
+ bulkContainer.executeBulkOperations(operations, bulkExecutionOptions));
assertThat(bulkResponses.size()).isEqualTo(operations.size());
assertThat(bulkResponses.get(0).getResponse().getStatusCode()).isEqualTo(HttpResponseStatus.CREATED.code());
- assertThat(this.getSessionToken((bulkResponses.get(0).getResponse().getSessionToken())).getLSN())
- .isGreaterThan(sessionToken.getLSN());
+ assertSessionTokenAdvanced(bulkResponses.get(0), sessionToken, "create");
assertThat(bulkResponses.get(1).getResponse().getStatusCode()).isEqualTo(HttpResponseStatus.OK.code());
- assertThat(this.getSessionToken((bulkResponses.get(1).getResponse().getSessionToken())).getLSN())
- .isGreaterThan(sessionToken.getLSN());
+ assertSessionTokenAdvanced(bulkResponses.get(1), sessionToken, "replace");
assertThat(bulkResponses.get(2).getResponse().getStatusCode()).isEqualTo(HttpResponseStatus.CREATED.code());
- assertThat(this.getSessionToken((bulkResponses.get(2).getResponse().getSessionToken())).getLSN())
- .isGreaterThan(sessionToken.getLSN());
+ assertSessionTokenAdvanced(bulkResponses.get(2), sessionToken, "upsert");
assertThat(bulkResponses.get(3).getResponse().getStatusCode()).isEqualTo(HttpResponseStatus.NO_CONTENT.code());
- assertThat(this.getSessionToken((bulkResponses.get(2).getResponse().getSessionToken())).getLSN())
- .isGreaterThan(sessionToken.getLSN());
+ assertSessionTokenAdvanced(bulkResponses.get(3), sessionToken, "delete");
+ }
+
+ private void assertSessionTokenAdvanced(
+ com.azure.cosmos.models.CosmosBulkOperationResponse