Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions eng/pipelines/templates/jobs/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ jobs:
os: linux

steps:
# APIView compares the PR source with origin/<target>, including their merge base.
- checkout: self
fetchDepth: 0
fetchTags: false

- task: UsePythonVersion@0
displayName: "Use Python $(PythonVersion)"
condition: succeededOrFailed()
Expand Down
57 changes: 31 additions & 26 deletions eng/pipelines/templates/jobs/live.tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ parameters:
- name: PersistOidcToken
type: boolean
default: false
- name: DisableAzureResourceCreation
type: boolean
default: false

jobs:
- job:
Expand Down Expand Up @@ -125,30 +128,31 @@ jobs:
parameters:
BuildTargetingString: ${{ parameters.BuildTargetingString }}

- template: /eng/common/TestResources/build-test-resource-config.yml
parameters:
SubscriptionConfiguration: ${{ parameters.CloudConfig.SubscriptionConfiguration }}
SubscriptionConfigurations: ${{ parameters.CloudConfig.SubscriptionConfigurations }}
${{ if parameters.UseFederatedAuth }}:
SubscriptionConfigurationFilePaths: ${{ parameters.CloudConfig.SubscriptionConfigurationFilePaths }}
EnvVars:
Pool: $(Pool)
${{ insert }}: ${{ parameters.EnvVars }}

- ${{ each directory in coalesce(parameters.TestResourceDirectories, split(parameters.ServiceDirectory, '|')) }}:
- template: /eng/common/TestResources/deploy-test-resources.yml
- ${{ if not(parameters.DisableAzureResourceCreation) }}:
- template: /eng/common/TestResources/build-test-resource-config.yml
parameters:
Location: ${{ coalesce(parameters.Location, parameters.CloudConfig.Location) }}
ServiceDirectory: '${{ directory }}'
SubscriptionConfiguration: $(SubscriptionConfiguration)
ArmTemplateParameters: $(ArmTemplateParameters)
UseFederatedAuth: ${{ parameters.UseFederatedAuth }}
PersistOidcToken: ${{ parameters.PersistOidcToken }}
ServiceConnection: ${{ parameters.CloudConfig.ServiceConnection }}
SubscriptionConfiguration: ${{ parameters.CloudConfig.SubscriptionConfiguration }}
SubscriptionConfigurations: ${{ parameters.CloudConfig.SubscriptionConfigurations }}
${{ if parameters.UseFederatedAuth }}:
SubscriptionConfigurationFilePaths: ${{ parameters.CloudConfig.SubscriptionConfigurationFilePaths }}
EnvVars:
Pool: $(Pool)
${{ insert }}: ${{ parameters.EnvVars }}

- ${{ each directory in coalesce(parameters.TestResourceDirectories, split(parameters.ServiceDirectory, '|')) }}:
- template: /eng/common/TestResources/deploy-test-resources.yml
parameters:
Location: ${{ coalesce(parameters.Location, parameters.CloudConfig.Location) }}
ServiceDirectory: '${{ directory }}'
SubscriptionConfiguration: $(SubscriptionConfiguration)
ArmTemplateParameters: $(ArmTemplateParameters)
UseFederatedAuth: ${{ parameters.UseFederatedAuth }}
PersistOidcToken: ${{ parameters.PersistOidcToken }}
ServiceConnection: ${{ parameters.CloudConfig.ServiceConnection }}
EnvVars:
Pool: $(Pool)
${{ insert }}: ${{ parameters.EnvVars }}

# This is a heuristic to detect compiled requirements in the service
# directory. If compiled requirements are found, install dependencies for
# and configure cibuildwheel.
Expand Down Expand Up @@ -224,12 +228,13 @@ jobs:
UseFederatedAuth: ${{ parameters.UseFederatedAuth }}
ServiceConnection: ${{ parameters.CloudConfig.ServiceConnection }}

- ${{ each directory in coalesce(parameters.TestResourceDirectories, split(parameters.ServiceDirectory, '|')) }}:
- template: /eng/common/TestResources/remove-test-resources.yml
parameters:
ServiceDirectory: '${{ directory }}'
SubscriptionConfiguration: $(SubscriptionConfiguration)
UseFederatedAuth: ${{ parameters.UseFederatedAuth }}
ServiceConnection: ${{ parameters.CloudConfig.ServiceConnection }}
- ${{ if not(parameters.DisableAzureResourceCreation) }}:
- ${{ each directory in coalesce(parameters.TestResourceDirectories, split(parameters.ServiceDirectory, '|')) }}:
- template: /eng/common/TestResources/remove-test-resources.yml
parameters:
ServiceDirectory: '${{ directory }}'
SubscriptionConfiguration: $(SubscriptionConfiguration)
UseFederatedAuth: ${{ parameters.UseFederatedAuth }}
ServiceConnection: ${{ parameters.CloudConfig.ServiceConnection }}

- ${{ parameters.PostSteps }}
115 changes: 64 additions & 51 deletions eng/pipelines/templates/stages/archetype-sdk-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ parameters:
- name: AdditionalMatrixConfigs
type: object
default: []
- name: FixedAccountMatrixConfigs
type: object
default: []
- name: FixedAccountPreSteps
type: object
default: []
- name: MatrixFilters
type: object
default: []
Expand Down Expand Up @@ -116,57 +122,64 @@ extends:
- group: ${{ group }}
dependsOn: []
jobs:
- template: /eng/common/pipelines/templates/jobs/generate-job-matrix.yml
parameters:
SparseCheckoutPaths:
- sdk/${{ parameters.ServiceDirectory }}/**/*.json
JobTemplatePath: /eng/pipelines/templates/jobs/live.tests.yml
OsVmImage: ubuntu-24.04
Pool: azsdk-pool
AdditionalParameters:
ServiceDirectory: ${{ parameters.ServiceDirectory }}
TestResourceDirectories: ${{ parameters.TestResourceDirectories }}
PreSteps:
- ${{ parameters.PlatformPreSteps }}
- ${{ parameters.PreSteps }}
PostSteps:
- ${{ parameters.PlatformPostSteps }}
- ${{ parameters.PostSteps }}
EnvVars: ${{ parameters.EnvVars }}
MaxParallel: ${{ parameters.MaxParallel }}
BeforeTestSteps: ${{ parameters.BeforeTestSteps }}
AfterTestSteps: ${{ parameters.AfterTestSteps }}
AdditionalTestArgs: ${{ parameters.AdditionalTestArgs }}
BuildTargetingString: ${{ package }}
TestMarkArgument: ${{ parameters.TestMarkArgument }}
InjectedPackages: ${{ parameters.InjectedPackages }}
BuildDocs: ${{ parameters.BuildDocs }}
TestTimeoutInMinutes: ${{ parameters.TestTimeoutInMinutes }}
TestProxy: ${{ parameters.TestProxy }}
CheckEnv: ${{ parameters.CheckEnv }}
UseFederatedAuth: ${{ parameters.UseFederatedAuth }}
PersistOidcToken: ${{ parameters.PersistOidcToken }}
MatrixConfigs:
# Enumerate platforms and additional platforms based on supported clouds (sparse platform<-->cloud matrix).
- ${{ each config in parameters.MatrixConfigs }}:
- ${{ config }}
- ${{ each config in parameters.AdditionalMatrixConfigs }}:
- ${{ config }}
MatrixFilters:
- ${{ each cloudFilter in cloud.value.MatrixFilters }}:
- ${{ cloudFilter }}
- ${{ parameters.MatrixFilters }}
MatrixReplace:
- ${{ each cloudReplace in cloud.value.MatrixReplace }}:
- ${{ cloudReplace }}
- ${{ parameters.MatrixReplace }}
CloudConfig:
SubscriptionConfiguration: ${{ cloud.value.SubscriptionConfiguration }}
SubscriptionConfigurations: ${{ cloud.value.SubscriptionConfigurations }}
Location: ${{ coalesce(cloud.value.Location, parameters.Location) }}
Cloud: ${{ cloud.key }}
ServiceConnection: ${{ coalesce(cloud.value.ServiceConnection, lower(format('azure-sdk-tests-{0}', cloud.key))) }}
SubscriptionConfigurationFilePaths: ${{ cloud.value.SubscriptionConfigurationFilePaths }}
- ${{ each matrixType in split('provisioned,fixed', ',') }}:
- ${{ if or(eq(matrixType, 'provisioned'), ne(length(parameters.FixedAccountMatrixConfigs), 0)) }}:
- template: /eng/common/pipelines/templates/jobs/generate-job-matrix.yml
parameters:
${{ if eq(matrixType, 'fixed') }}:
GenerateJobName: generate_fixed_account_job_matrix
SparseCheckoutPaths:
- sdk/${{ parameters.ServiceDirectory }}/**/*.json
JobTemplatePath: /eng/pipelines/templates/jobs/live.tests.yml
OsVmImage: ubuntu-24.04
Pool: azsdk-pool
AdditionalParameters:
ServiceDirectory: ${{ parameters.ServiceDirectory }}
TestResourceDirectories: ${{ parameters.TestResourceDirectories }}
DisableAzureResourceCreation: ${{ eq(matrixType, 'fixed') }}
PreSteps:
- ${{ parameters.PlatformPreSteps }}
- ${{ parameters.PreSteps }}
- ${{ if eq(matrixType, 'fixed') }}:
- ${{ parameters.FixedAccountPreSteps }}
PostSteps:
- ${{ parameters.PlatformPostSteps }}
- ${{ parameters.PostSteps }}
EnvVars: ${{ parameters.EnvVars }}
MaxParallel: ${{ parameters.MaxParallel }}
BeforeTestSteps: ${{ parameters.BeforeTestSteps }}
AfterTestSteps: ${{ parameters.AfterTestSteps }}
AdditionalTestArgs: ${{ parameters.AdditionalTestArgs }}
BuildTargetingString: ${{ package }}
TestMarkArgument: ${{ parameters.TestMarkArgument }}
InjectedPackages: ${{ parameters.InjectedPackages }}
BuildDocs: ${{ parameters.BuildDocs }}
TestTimeoutInMinutes: ${{ parameters.TestTimeoutInMinutes }}
TestProxy: ${{ parameters.TestProxy }}
CheckEnv: ${{ parameters.CheckEnv }}
UseFederatedAuth: ${{ and(parameters.UseFederatedAuth, ne(matrixType, 'fixed')) }}
PersistOidcToken: ${{ parameters.PersistOidcToken }}
MatrixConfigs:
- ${{ if eq(matrixType, 'fixed') }}:
- ${{ parameters.FixedAccountMatrixConfigs }}
- ${{ else }}:
- ${{ parameters.MatrixConfigs }}
- ${{ parameters.AdditionalMatrixConfigs }}
MatrixFilters:
- ${{ each cloudFilter in cloud.value.MatrixFilters }}:
- ${{ cloudFilter }}
- ${{ parameters.MatrixFilters }}
MatrixReplace:
- ${{ each cloudReplace in cloud.value.MatrixReplace }}:
- ${{ cloudReplace }}
- ${{ parameters.MatrixReplace }}
CloudConfig:
SubscriptionConfiguration: ${{ cloud.value.SubscriptionConfiguration }}
SubscriptionConfigurations: ${{ cloud.value.SubscriptionConfigurations }}
Location: ${{ coalesce(cloud.value.Location, parameters.Location) }}
Cloud: ${{ cloud.key }}
ServiceConnection: ${{ coalesce(cloud.value.ServiceConnection, lower(format('azure-sdk-tests-{0}', cloud.key))) }}
SubscriptionConfigurationFilePaths: ${{ cloud.value.SubscriptionConfigurationFilePaths }}

# Analyze-weekly runs only on scheduled runs that start on a weekend (Sat/Sun, UTC).
- ${{ if eq(variables['Build.Reason'], 'Schedule') }}:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ def run_powershell(script, **environment):
)


def test_analysis_checkout_has_history_for_api_comparison():
jobs = load_template("jobs/ci.yml")
analyze = next(job for job in jobs["jobs"] if job.get("job") == "Analyze")
assert analyze["steps"][0] == {"checkout": "self", "fetchDepth": 0, "fetchTags": False}


def test_rust_support_is_disabled_by_default_and_forwarded():
archetype = load_template("stages/archetype-sdk-client.yml")
jobs = load_template("jobs/ci.yml")
Expand Down
27 changes: 27 additions & 0 deletions sdk/cosmos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,33 @@ Legacy DocumentDB SDK.

Use this package if you are maintaining legacy applications that can't be upgrade for the latest features and optimizations.

## Live-test accounts

`tests.yml` runs two matrices through the shared Python live-test templates:

- `fixed-account-platform-matrix.json` contains key-auth jobs with an `AccountSelector`.
These jobs omit subscription configuration, Azure resource deployment, provisioning login,
and resource cleanup at template expansion. Tests run in plain PowerShell without a
service-connection login or a `CI_HAS_DEPLOYED_RESOURCES` requirement.
- `live-platform-matrix.json` retains the AAD and GSI jobs. Their provisioning, AAD role
assignments, federated test authentication, and cleanup are unchanged.

The fixed-account pre-step selects credentials from the secret
`sub-config-cosmos-azure-cloud-test-resources` in the existing Cosmos live-test variable
group. It exports `ACCOUNT_HOST`, `ACCOUNT_KEY`, and optional `SECONDARY_ACCOUNT_KEY`
directly, registering each with the Azure Pipelines log scrubber before setting the
environment-visible variable. The aggregate JSON is mapped only into the resolver task.
A missing selector or invalid configuration fails the job rather than falling back to
provisioning. There is no post-deployment credential rebinding.

When adding a key-auth job, put it in the fixed-account matrix and specify its selector.
Set client-side options such as `AZURE_COSMOS_ENABLE_CIRCUIT_BREAKER` in that matrix;
fixed jobs do not receive Bicep outputs. Retained `ArmConfig` labels preserve existing
job names and filters but do not cause deployment.

The `pipeline` directory contains the CI runtime resolver template and PowerShell
script; both are required by the fixed-account jobs.

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Expand Down
66 changes: 66 additions & 0 deletions sdk/cosmos/azure-cosmos/tests/_retry_test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# The MIT License (MIT)
# Copyright (c) Microsoft Corporation. All rights reserved.

from contextlib import contextmanager
from unittest.mock import patch
from urllib.parse import urlsplit, urlunsplit

from azure.cosmos import CosmosClient, DatabaseAccount
from azure.cosmos._global_endpoint_manager import _GlobalEndpointManager


def configure_retry_regions(location_cache, read_regions, write_regions=None):
"""Build a complete synthetic topology, including the derived routing maps."""
write_regions = list(write_regions or read_regions[:1])
account = DatabaseAccount()
account._ReadableLocations = [
{"name": region, "databaseAccountEndpoint": retry_endpoint(region)} for region in read_regions
]
account._WritableLocations = [
{"name": region, "databaseAccountEndpoint": retry_endpoint(region)} for region in write_regions
]
account._EnableMultipleWritableLocations = len(write_regions) > 1
location_cache.connection_policy.PreferredLocations = list(read_regions)
location_cache.connection_policy.UseMultipleWriteLocations = len(write_regions) > 1
location_cache.enable_multiple_writable_locations = len(write_regions) > 1
location_cache.location_unavailability_info_by_endpoint.clear()
location_cache.perform_on_database_account_read(account)


def retry_endpoint(region):
return "https://{}.retry-test.invalid/".format(region.lower().replace(" ", ""))


def send_to_test_host(send, host, request, **kwargs):
"""Keep synthetic routing observable to policies, but never send it over the network."""
url = request.url
parsed = urlsplit(url)
if parsed.hostname and parsed.hostname.endswith(".retry-test.invalid"):
target = urlsplit(host)
request.url = urlunsplit((target.scheme, target.netloc, parsed.path, parsed.query, parsed.fragment))
# Match the SDK's loopback emulator TLS behavior after replacing the synthetic host.
if target.hostname in ("localhost", "127.0.0.1"):
kwargs["connection_verify"] = False
try:
return send(request, **kwargs)
finally:
request.url = url


@contextmanager
def retry_test_client(host, key):
# Prevent startup health checks from racing the synthetic topology installed below.
with patch.object(_GlobalEndpointManager, "_start_background_refresh"):
client = CosmosClient(host, key)
try:
manager = client.client_connection._global_endpoint_manager
transport = client.client_connection.pipeline_client._pipeline._transport
send = transport.send
with patch.object(manager, "refresh_endpoint_list"), patch.object(
transport,
"send",
side_effect=lambda request, **kwargs: send_to_test_host(send, host, request, **kwargs),
):
yield client
finally:
client.close()
20 changes: 20 additions & 0 deletions sdk/cosmos/azure-cosmos/tests/_split_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import traceback
import unittest
import uuid
from collections import Counter
from contextlib import contextmanager

from azure.cosmos import _base
Expand All @@ -22,13 +23,32 @@
"actual_count",
"missing_count",
"unexpected_count",
"duplicate_count",
"order_matches",
"ids_match",
"token_matches",
"lsn_not_regressed",
"expected_range_present",
)


def assert_change_feed_ids(actual_ids, expected_ids, diagnostics):
"""Check all expected changes exactly once, without cross-partition ordering."""
actual = Counter(actual_ids)
expected = Counter(expected_ids)
diagnostics.update(
expected_count=len(expected_ids),
actual_count=len(actual_ids),
missing_count=sum((expected - actual).values()),
unexpected_count=sum((actual - expected).values()),
duplicate_count=sum(count - 1 for count in actual.values() if count > 1),
ids_match=actual == expected,
order_matches=actual_ids == expected_ids,
)
if actual != expected:
raise AssertionError("Change feed IDs differ from the expected multiset")


def create_item(hpk):
if hpk:
item = {
Expand Down
Loading
Loading