From 0e5e58eea63f8c97614d8c4e813f9356d01fd4f6 Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Wed, 11 Nov 2026 12:35:58 +0100 Subject: [PATCH 1/2] Set the MongoDB server version of the MongoDB sample, which the Azure CLI ignores deploy.sh of web-app-cosmosdb-mongodb-api (Python and .NET) creates the account with az cosmosdb create --server-version 7.0. Since azure-cli 2.90.0 the CLI sends that value as a ServerVersion property, which the service ignores, so the account runs MongoDB server version 3.6, and the MongoDB drivers of both web apps refuse it. On Azure, the Python app failed with "reports wire version 6, but this version of PyMongo requires at least 9 (MongoDB 4.4)" and answered HTTP 503. When the account reports a server version other than MONGODB_API_VERSION, the scripts now set serverVersion with az rest and wait until the account reports it, which takes about a minute. An account that already existed, such as one an earlier run left at 3.6, can hold data, and the MongoDB endpoint of such an account went on serving 3.6 for minutes after the account reported 7.0, so the web app deployed in the meantime failed to start. After upgrading such an account, the scripts wait with mongosh until the endpoint serves at least MongoDB 4.4, and only then deploy the web app. They check for mongosh before they change the account, and both READMEs list it for that case. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FvNuJLfY2KkMwaymfFnz7R --- .../dotnet/README.md | 1 + .../dotnet/scripts/deploy.sh | 113 ++++++++++++++++++ .../python/README.md | 1 + .../python/scripts/deploy.sh | 113 ++++++++++++++++++ 4 files changed, 228 insertions(+) diff --git a/samples/web-app-cosmosdb-mongodb-api/dotnet/README.md b/samples/web-app-cosmosdb-mongodb-api/dotnet/README.md index 98597c4..af364cd 100644 --- a/samples/web-app-cosmosdb-mongodb-api/dotnet/README.md +++ b/samples/web-app-cosmosdb-mongodb-api/dotnet/README.md @@ -34,6 +34,7 @@ The web app enables users to plan and manage vacation activities, with all data - [.NET SDK 10.0](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) - [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/) - [MongoDB C# Driver](https://www.mongodb.com/docs/drivers/csharp/current/) +- [mongosh](https://www.mongodb.com/docs/mongodb-shell/install/), if you run `scripts/deploy.sh` over an earlier deployment whose Cosmos DB account does not run MongoDB server version 7.0 yet: after upgrading the account, the script waits with mongosh until its MongoDB endpoint serves the new version, so that the web app starts at once. - [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep), if you plan to install the sample via Bicep. - [Terraform](https://developer.hashicorp.com/terraform/downloads), if you plan to install the sample via Terraform. diff --git a/samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh b/samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh index b977363..fa182bb 100755 --- a/samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh +++ b/samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh @@ -62,6 +62,7 @@ az cosmosdb show \ --only-show-errors &>/dev/null if [[ $? != 0 ]]; then + COSMOSDB_ACCOUNT_EXISTED="false" echo "No [$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" echo "Creating [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group..." @@ -82,6 +83,7 @@ if [[ $? != 0 ]]; then exit 1 fi else + COSMOSDB_ACCOUNT_EXISTED="true" echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" fi @@ -101,6 +103,81 @@ else exit 1 fi +# Make sure the CosmosDB account runs the MongoDB server version of MONGODB_API_VERSION. The Azure CLI sends the +# --server-version of az cosmosdb create and az cosmosdb update as a ServerVersion property, which the service +# ignores, so the account keeps the default server version, 3.6, which the MongoDB driver of the web app refuses. +# The script sets the serverVersion property itself, with the API version the Azure CLI uses, and waits until the +# account reports the new server version, which takes about a minute. After upgrading an account that already +# existed, the script also waits with mongosh for the MongoDB endpoint, before it deploys the web app (see below). +echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account MongoDB server version in the [$RESOURCE_GROUP_NAME] resource group..." +SERVER_VERSION=$(az cosmosdb show \ + --name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --query "apiProperties.serverVersion" \ + --output tsv \ + --only-show-errors) + +if [ -n "$SERVER_VERSION" ]; then + echo "MongoDB server version retrieved successfully: $SERVER_VERSION" +else + echo "Failed to retrieve MongoDB server version." + exit 1 +fi + +if [[ "$SERVER_VERSION" != "$MONGODB_API_VERSION" ]]; then + if [[ "$COSMOSDB_ACCOUNT_EXISTED" == "true" ]]; then + if ! command -v mongosh &>/dev/null; then + echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account runs MongoDB server version [$SERVER_VERSION]: upgrading it to [$MONGODB_API_VERSION] needs mongosh, to wait for its MongoDB endpoint before deploying the web app." + echo "Install mongosh (https://www.mongodb.com/docs/mongodb-shell/install/) and run the script again." + exit 1 + fi + fi + + echo "Upgrading [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]..." + az rest \ + --method patch \ + --url "${COSMOSDB_ACCOUNT_ID}?api-version=2026-03-15" \ + --body "{\"properties\": {\"apiProperties\": {\"serverVersion\": \"$MONGODB_API_VERSION\"}}}" \ + --only-show-errors 1>/dev/null + + if [ $? -ne 0 ]; then + echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]" + exit 1 + fi + + for ATTEMPT in $(seq 1 60); do + SERVER_VERSION=$(az cosmosdb show \ + --name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --query "apiProperties.serverVersion" \ + --output tsv \ + --only-show-errors) + PROVISIONING_STATE=$(az cosmosdb show \ + --name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --query "provisioningState" \ + --output tsv \ + --only-show-errors) + + if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then + break + fi + echo "Waiting for the upgrade of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account (attempt $ATTEMPT of 60)..." + sleep 10 + done + + if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then + COSMOSDB_ACCOUNT_UPGRADED="true" + echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully upgraded to MongoDB server version [$MONGODB_API_VERSION]" + else + echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account: it still runs MongoDB server version [$SERVER_VERSION]" + exit 1 + fi +else + COSMOSDB_ACCOUNT_UPGRADED="false" + echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already runs MongoDB server version [$MONGODB_API_VERSION]" +fi + # Retrieve document endpoint echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account document endpoint in the [$RESOURCE_GROUP_NAME] resource group..." DOCUMENT_ENDPOINT=$(az cosmosdb show \ @@ -909,6 +986,42 @@ else echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet already exist" fi +# An account that already existed before this run can hold the data of an earlier deployment. Microsoft documents that +# an upgrade of the MongoDB server version takes around 15 minutes to complete, and the MongoDB endpoint of such an +# account can go on serving the old version for most of that time, although the account already reports the new one. +# The MongoDB driver of the web app refuses the old version, so a web app deployed in the meantime fails to start. +# After upgrading such an account, the script waits until the endpoint serves at least MongoDB 4.4 (wire version 9), +# which the MongoDB drivers of both web apps accept, before it deploys the web app. +if [[ "$COSMOSDB_ACCOUNT_EXISTED" == "true" && "$COSMOSDB_ACCOUNT_UPGRADED" == "true" ]]; then + if [ -z "$COSMOSDB_CONNECTION_STRING" ]; then + echo "Failed: no connection string to wait for the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account." + exit 1 + fi + + echo "Waiting for the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to serve MongoDB server version [$MONGODB_API_VERSION]..." + MONGODB_ENDPOINT_READY="false" + + for ATTEMPT in $(seq 1 40); do + mongosh "$COSMOSDB_CONNECTION_STRING" \ + --quiet \ + --eval 'if (db.hello().maxWireVersion < 9) { quit(1) }' &>/dev/null + + if [[ $? == 0 ]]; then + MONGODB_ENDPOINT_READY="true" + break + fi + echo "The MongoDB endpoint does not serve the new MongoDB server version yet (attempt $ATTEMPT of 40)..." + sleep 30 + done + + if [[ "$MONGODB_ENDPOINT_READY" == "true" ]]; then + echo "The MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account serves MongoDB server version [$MONGODB_API_VERSION]" + else + echo "Failed: the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account does not serve MongoDB server version [$MONGODB_API_VERSION] yet. Run the script again in a few minutes." + exit 1 + fi +fi + # Change current directory to source folder cd "../src" || exit diff --git a/samples/web-app-cosmosdb-mongodb-api/python/README.md b/samples/web-app-cosmosdb-mongodb-api/python/README.md index 38e8498..cca1853 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/README.md +++ b/samples/web-app-cosmosdb-mongodb-api/python/README.md @@ -34,6 +34,7 @@ The web app enables users to plan and manage vacation activities, with all data - [Python 3.11+](https://www.python.org/downloads/) - [Flask](https://flask.palletsprojects.com/) - [pymongo](https://pymongo.readthedocs.io/en/stable/) +- [mongosh](https://www.mongodb.com/docs/mongodb-shell/install/), if you run `scripts/deploy.sh` over an earlier deployment whose Cosmos DB account does not run MongoDB server version 7.0 yet: after upgrading the account, the script waits with mongosh until its MongoDB endpoint serves the new version, so that the web app starts at once. - [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep), if you plan to install the sample via Bicep. - [Terraform](https://developer.hashicorp.com/terraform/downloads), if you plan to install the sample via Terraform. diff --git a/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh b/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh index e7ceb62..7c42ffa 100755 --- a/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh +++ b/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh @@ -62,6 +62,7 @@ az cosmosdb show \ --only-show-errors &>/dev/null if [[ $? != 0 ]]; then + COSMOSDB_ACCOUNT_EXISTED="false" echo "No [$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" echo "Creating [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group..." @@ -82,6 +83,7 @@ if [[ $? != 0 ]]; then exit 1 fi else + COSMOSDB_ACCOUNT_EXISTED="true" echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" fi @@ -101,6 +103,81 @@ else exit 1 fi +# Make sure the CosmosDB account runs the MongoDB server version of MONGODB_API_VERSION. The Azure CLI sends the +# --server-version of az cosmosdb create and az cosmosdb update as a ServerVersion property, which the service +# ignores, so the account keeps the default server version, 3.6, which the MongoDB driver of the web app refuses. +# The script sets the serverVersion property itself, with the API version the Azure CLI uses, and waits until the +# account reports the new server version, which takes about a minute. After upgrading an account that already +# existed, the script also waits with mongosh for the MongoDB endpoint, before it deploys the web app (see below). +echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account MongoDB server version in the [$RESOURCE_GROUP_NAME] resource group..." +SERVER_VERSION=$(az cosmosdb show \ + --name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --query "apiProperties.serverVersion" \ + --output tsv \ + --only-show-errors) + +if [ -n "$SERVER_VERSION" ]; then + echo "MongoDB server version retrieved successfully: $SERVER_VERSION" +else + echo "Failed to retrieve MongoDB server version." + exit 1 +fi + +if [[ "$SERVER_VERSION" != "$MONGODB_API_VERSION" ]]; then + if [[ "$COSMOSDB_ACCOUNT_EXISTED" == "true" ]]; then + if ! command -v mongosh &>/dev/null; then + echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account runs MongoDB server version [$SERVER_VERSION]: upgrading it to [$MONGODB_API_VERSION] needs mongosh, to wait for its MongoDB endpoint before deploying the web app." + echo "Install mongosh (https://www.mongodb.com/docs/mongodb-shell/install/) and run the script again." + exit 1 + fi + fi + + echo "Upgrading [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]..." + az rest \ + --method patch \ + --url "${COSMOSDB_ACCOUNT_ID}?api-version=2026-03-15" \ + --body "{\"properties\": {\"apiProperties\": {\"serverVersion\": \"$MONGODB_API_VERSION\"}}}" \ + --only-show-errors 1>/dev/null + + if [ $? -ne 0 ]; then + echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]" + exit 1 + fi + + for ATTEMPT in $(seq 1 60); do + SERVER_VERSION=$(az cosmosdb show \ + --name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --query "apiProperties.serverVersion" \ + --output tsv \ + --only-show-errors) + PROVISIONING_STATE=$(az cosmosdb show \ + --name $COSMOSDB_ACCOUNT_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --query "provisioningState" \ + --output tsv \ + --only-show-errors) + + if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then + break + fi + echo "Waiting for the upgrade of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account (attempt $ATTEMPT of 60)..." + sleep 10 + done + + if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then + COSMOSDB_ACCOUNT_UPGRADED="true" + echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully upgraded to MongoDB server version [$MONGODB_API_VERSION]" + else + echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account: it still runs MongoDB server version [$SERVER_VERSION]" + exit 1 + fi +else + COSMOSDB_ACCOUNT_UPGRADED="false" + echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already runs MongoDB server version [$MONGODB_API_VERSION]" +fi + # Retrieve document endpoint echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account document endpoint in the [$RESOURCE_GROUP_NAME] resource group..." DOCUMENT_ENDPOINT=$(az cosmosdb show \ @@ -909,6 +986,42 @@ else echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet already exist" fi +# An account that already existed before this run can hold the data of an earlier deployment. Microsoft documents that +# an upgrade of the MongoDB server version takes around 15 minutes to complete, and the MongoDB endpoint of such an +# account can go on serving the old version for most of that time, although the account already reports the new one. +# The MongoDB driver of the web app refuses the old version, so a web app deployed in the meantime fails to start. +# After upgrading such an account, the script waits until the endpoint serves at least MongoDB 4.4 (wire version 9), +# which the MongoDB drivers of both web apps accept, before it deploys the web app. +if [[ "$COSMOSDB_ACCOUNT_EXISTED" == "true" && "$COSMOSDB_ACCOUNT_UPGRADED" == "true" ]]; then + if [ -z "$COSMOSDB_CONNECTION_STRING" ]; then + echo "Failed: no connection string to wait for the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account." + exit 1 + fi + + echo "Waiting for the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to serve MongoDB server version [$MONGODB_API_VERSION]..." + MONGODB_ENDPOINT_READY="false" + + for ATTEMPT in $(seq 1 40); do + mongosh "$COSMOSDB_CONNECTION_STRING" \ + --quiet \ + --eval 'if (db.hello().maxWireVersion < 9) { quit(1) }' &>/dev/null + + if [[ $? == 0 ]]; then + MONGODB_ENDPOINT_READY="true" + break + fi + echo "The MongoDB endpoint does not serve the new MongoDB server version yet (attempt $ATTEMPT of 40)..." + sleep 30 + done + + if [[ "$MONGODB_ENDPOINT_READY" == "true" ]]; then + echo "The MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account serves MongoDB server version [$MONGODB_API_VERSION]" + else + echo "Failed: the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account does not serve MongoDB server version [$MONGODB_API_VERSION] yet. Run the script again in a few minutes." + exit 1 + fi +fi + # Change current directory to source folder cd "../src" || exit From dc61c771160c7bca4993154941d85552790e808e Mon Sep 17 00:00:00 2001 From: Paolo Salvatori Date: Wed, 11 Nov 2026 13:31:08 +0100 Subject: [PATCH 2/2] Start the MongoDB sample's web apps without the database, and drop the mongosh check The mongosh check that deploy.sh ran after upgrading an existing account reached the account's public MongoDB endpoint from the deploying machine. On Azure, that endpoint answered "Request blocked by network firewall" for more than 30 minutes after an upgrade, while the web app, which reaches the account through the private endpoint, was already serving pages, so the check stopped a deployment that would have worked. The check and its mongosh prerequisite are removed. Both web apps now start without the database instead. The Python app creates its MongoDB client on the first request that needs it, under a lock, closes the client when the database setup fails, and shows "The database is not available. Try again in a minute." until the database answers; a missing connection string still stops it at startup. The .NET app initializes the store in a background service that retries every 15 seconds, and its pages show the same message instead of an error page; /health answers 503 until the database answers. A web app deployed while the MongoDB endpoint still serves the old server version therefore starts, and works as soon as the endpoint does. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FvNuJLfY2KkMwaymfFnz7R --- .../dotnet/README.md | 1 - .../dotnet/scripts/deploy.sh | 52 +----------- .../dotnet/src/Pages/Delete.cshtml.cs | 14 +++- .../dotnet/src/Pages/Index.cshtml.cs | 37 ++++++--- .../dotnet/src/Services/StoreInitializer.cs | 43 +++++----- .../python/README.md | 1 - .../python/scripts/deploy.sh | 52 +----------- .../python/src/app.py | 80 ++++++++++++++----- .../python/src/mongodb.py | 15 ++-- 9 files changed, 136 insertions(+), 159 deletions(-) diff --git a/samples/web-app-cosmosdb-mongodb-api/dotnet/README.md b/samples/web-app-cosmosdb-mongodb-api/dotnet/README.md index af364cd..98597c4 100644 --- a/samples/web-app-cosmosdb-mongodb-api/dotnet/README.md +++ b/samples/web-app-cosmosdb-mongodb-api/dotnet/README.md @@ -34,7 +34,6 @@ The web app enables users to plan and manage vacation activities, with all data - [.NET SDK 10.0](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) - [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/) - [MongoDB C# Driver](https://www.mongodb.com/docs/drivers/csharp/current/) -- [mongosh](https://www.mongodb.com/docs/mongodb-shell/install/), if you run `scripts/deploy.sh` over an earlier deployment whose Cosmos DB account does not run MongoDB server version 7.0 yet: after upgrading the account, the script waits with mongosh until its MongoDB endpoint serves the new version, so that the web app starts at once. - [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep), if you plan to install the sample via Bicep. - [Terraform](https://developer.hashicorp.com/terraform/downloads), if you plan to install the sample via Terraform. diff --git a/samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh b/samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh index fa182bb..da9454c 100755 --- a/samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh +++ b/samples/web-app-cosmosdb-mongodb-api/dotnet/scripts/deploy.sh @@ -62,7 +62,6 @@ az cosmosdb show \ --only-show-errors &>/dev/null if [[ $? != 0 ]]; then - COSMOSDB_ACCOUNT_EXISTED="false" echo "No [$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" echo "Creating [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group..." @@ -83,7 +82,6 @@ if [[ $? != 0 ]]; then exit 1 fi else - COSMOSDB_ACCOUNT_EXISTED="true" echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" fi @@ -107,8 +105,8 @@ fi # --server-version of az cosmosdb create and az cosmosdb update as a ServerVersion property, which the service # ignores, so the account keeps the default server version, 3.6, which the MongoDB driver of the web app refuses. # The script sets the serverVersion property itself, with the API version the Azure CLI uses, and waits until the -# account reports the new server version, which takes about a minute. After upgrading an account that already -# existed, the script also waits with mongosh for the MongoDB endpoint, before it deploys the web app (see below). +# account reports the new server version, which takes about a minute. The MongoDB endpoint can go on serving the old +# version for a while after that: the web app starts without the database and connects once the endpoint switched. echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account MongoDB server version in the [$RESOURCE_GROUP_NAME] resource group..." SERVER_VERSION=$(az cosmosdb show \ --name $COSMOSDB_ACCOUNT_NAME \ @@ -125,14 +123,6 @@ else fi if [[ "$SERVER_VERSION" != "$MONGODB_API_VERSION" ]]; then - if [[ "$COSMOSDB_ACCOUNT_EXISTED" == "true" ]]; then - if ! command -v mongosh &>/dev/null; then - echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account runs MongoDB server version [$SERVER_VERSION]: upgrading it to [$MONGODB_API_VERSION] needs mongosh, to wait for its MongoDB endpoint before deploying the web app." - echo "Install mongosh (https://www.mongodb.com/docs/mongodb-shell/install/) and run the script again." - exit 1 - fi - fi - echo "Upgrading [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]..." az rest \ --method patch \ @@ -167,14 +157,12 @@ if [[ "$SERVER_VERSION" != "$MONGODB_API_VERSION" ]]; then done if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then - COSMOSDB_ACCOUNT_UPGRADED="true" echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully upgraded to MongoDB server version [$MONGODB_API_VERSION]" else echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account: it still runs MongoDB server version [$SERVER_VERSION]" exit 1 fi else - COSMOSDB_ACCOUNT_UPGRADED="false" echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already runs MongoDB server version [$MONGODB_API_VERSION]" fi @@ -986,42 +974,6 @@ else echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet already exist" fi -# An account that already existed before this run can hold the data of an earlier deployment. Microsoft documents that -# an upgrade of the MongoDB server version takes around 15 minutes to complete, and the MongoDB endpoint of such an -# account can go on serving the old version for most of that time, although the account already reports the new one. -# The MongoDB driver of the web app refuses the old version, so a web app deployed in the meantime fails to start. -# After upgrading such an account, the script waits until the endpoint serves at least MongoDB 4.4 (wire version 9), -# which the MongoDB drivers of both web apps accept, before it deploys the web app. -if [[ "$COSMOSDB_ACCOUNT_EXISTED" == "true" && "$COSMOSDB_ACCOUNT_UPGRADED" == "true" ]]; then - if [ -z "$COSMOSDB_CONNECTION_STRING" ]; then - echo "Failed: no connection string to wait for the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account." - exit 1 - fi - - echo "Waiting for the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to serve MongoDB server version [$MONGODB_API_VERSION]..." - MONGODB_ENDPOINT_READY="false" - - for ATTEMPT in $(seq 1 40); do - mongosh "$COSMOSDB_CONNECTION_STRING" \ - --quiet \ - --eval 'if (db.hello().maxWireVersion < 9) { quit(1) }' &>/dev/null - - if [[ $? == 0 ]]; then - MONGODB_ENDPOINT_READY="true" - break - fi - echo "The MongoDB endpoint does not serve the new MongoDB server version yet (attempt $ATTEMPT of 40)..." - sleep 30 - done - - if [[ "$MONGODB_ENDPOINT_READY" == "true" ]]; then - echo "The MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account serves MongoDB server version [$MONGODB_API_VERSION]" - else - echo "Failed: the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account does not serve MongoDB server version [$MONGODB_API_VERSION] yet. Run the script again in a few minutes." - exit 1 - fi -fi - # Change current directory to source folder cd "../src" || exit diff --git a/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Pages/Delete.cshtml.cs b/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Pages/Delete.cshtml.cs index 84277d4..a3e0c33 100644 --- a/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Pages/Delete.cshtml.cs +++ b/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Pages/Delete.cshtml.cs @@ -13,9 +13,17 @@ public async Task OnPostAsync(string id, CancellationToken cancel { if (!string.IsNullOrWhiteSpace(id)) { - await store.DeleteAsync(id, cancellationToken); - logger.LogInformation("Activity deleted: {Id}", id); - TempData["Flash"] = "Activity deleted."; + try + { + await store.DeleteAsync(id, cancellationToken); + logger.LogInformation("Activity deleted: {Id}", id); + TempData["Flash"] = "Activity deleted."; + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + logger.LogError(ex, "Could not delete the activity"); + TempData["Flash"] = IndexModel.DatabaseUnavailable; + } } return RedirectToPage("/Index"); diff --git a/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Pages/Index.cshtml.cs b/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Pages/Index.cshtml.cs index 904f8c5..8d622b5 100644 --- a/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Pages/Index.cshtml.cs +++ b/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Pages/Index.cshtml.cs @@ -7,6 +7,9 @@ namespace VacationPlanner.Pages; public class IndexModel(IActivityStore store, ILogger logger) : PageModel { + /// Shown instead of an error page while the database is not reachable; the next request tries again. + internal const string DatabaseUnavailable = "The database is not available. Try again in a minute."; + public IReadOnlyList Activities { get; private set; } = []; /// Flash messages set by the previous request (the equivalent of Flask's flash()). @@ -20,7 +23,15 @@ public class IndexModel(IActivityStore store, ILogger logger) : Page public async Task OnGetAsync(CancellationToken cancellationToken) { - Activities = await store.ListAsync(cancellationToken); + try + { + Activities = await store.ListAsync(cancellationToken); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + logger.LogError(ex, "Could not read the activities"); + TempData["Flash"] = DatabaseUnavailable; + } } public async Task OnPostAsync(CancellationToken cancellationToken) @@ -29,17 +40,25 @@ public async Task OnPostAsync(CancellationToken cancellationToken var id = RowId?.Trim(); if (!string.IsNullOrEmpty(text)) { - if (!string.IsNullOrEmpty(id)) + try { - await store.UpdateAsync(id, text, cancellationToken); - logger.LogInformation("Activity updated: {Id}", id); - TempData["Flash"] = "Activity updated!"; + if (!string.IsNullOrEmpty(id)) + { + await store.UpdateAsync(id, text, cancellationToken); + logger.LogInformation("Activity updated: {Id}", id); + TempData["Flash"] = "Activity updated!"; + } + else + { + await store.AddAsync(text, cancellationToken); + logger.LogInformation("Activity added: {Activity}", text); + TempData["Flash"] = "Activity added!"; + } } - else + catch (Exception ex) when (ex is not OperationCanceledException) { - await store.AddAsync(text, cancellationToken); - logger.LogInformation("Activity added: {Activity}", text); - TempData["Flash"] = "Activity added!"; + logger.LogError(ex, "Could not save the activity"); + TempData["Flash"] = DatabaseUnavailable; } } diff --git a/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Services/StoreInitializer.cs b/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Services/StoreInitializer.cs index c74eb50..fe97370 100644 --- a/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Services/StoreInitializer.cs +++ b/samples/web-app-cosmosdb-mongodb-api/dotnet/src/Services/StoreInitializer.cs @@ -1,33 +1,40 @@ namespace VacationPlanner.Services; /// -/// Runs at startup with a bounded retry, so the app fails fast -/// (and the container exits) when the backing service never becomes reachable. +/// Runs in the background and retries it until it succeeds, so the app +/// starts even when the database is not reachable yet, for example while Azure Cosmos DB for MongoDB upgrades the +/// server version of the account. Until then, the pages show an error and /health answers 503. /// public sealed class StoreInitializer( IActivityStore store, ILogger logger, - int attempts = 1, - TimeSpan delay = default) : IHostedService + TimeSpan delay = default) : BackgroundService { - public async Task StartAsync(CancellationToken cancellationToken) + private readonly TimeSpan _delay = delay > TimeSpan.Zero ? delay : TimeSpan.FromSeconds(15); + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - for (var attempt = 1; ; attempt++) + try { - try - { - await store.InitializeAsync(cancellationToken); - logger.LogInformation("Activity store initialized after {Attempts} attempt(s).", attempt); - return; - } - catch (Exception ex) when (attempt < attempts && !cancellationToken.IsCancellationRequested) + for (var attempt = 1; !stoppingToken.IsCancellationRequested; attempt++) { - logger.LogWarning(ex, "Activity store not ready (attempt {Attempt}/{Attempts}); retrying in {Delay}s.", - attempt, attempts, delay.TotalSeconds); - await Task.Delay(delay, cancellationToken); + try + { + await store.InitializeAsync(stoppingToken); + logger.LogInformation("Activity store initialized after {Attempts} attempt(s).", attempt); + return; + } + catch (Exception ex) when (!stoppingToken.IsCancellationRequested) + { + logger.LogWarning(ex, "Activity store not ready (attempt {Attempt}); retrying in {Delay}s.", + attempt, _delay.TotalSeconds); + await Task.Delay(_delay, stoppingToken); + } } } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + // The app is stopping before the store was initialized: nothing left to do. + } } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } diff --git a/samples/web-app-cosmosdb-mongodb-api/python/README.md b/samples/web-app-cosmosdb-mongodb-api/python/README.md index cca1853..38e8498 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/README.md +++ b/samples/web-app-cosmosdb-mongodb-api/python/README.md @@ -34,7 +34,6 @@ The web app enables users to plan and manage vacation activities, with all data - [Python 3.11+](https://www.python.org/downloads/) - [Flask](https://flask.palletsprojects.com/) - [pymongo](https://pymongo.readthedocs.io/en/stable/) -- [mongosh](https://www.mongodb.com/docs/mongodb-shell/install/), if you run `scripts/deploy.sh` over an earlier deployment whose Cosmos DB account does not run MongoDB server version 7.0 yet: after upgrading the account, the script waits with mongosh until its MongoDB endpoint serves the new version, so that the web app starts at once. - [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep), if you plan to install the sample via Bicep. - [Terraform](https://developer.hashicorp.com/terraform/downloads), if you plan to install the sample via Terraform. diff --git a/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh b/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh index 7c42ffa..f57f77f 100755 --- a/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh +++ b/samples/web-app-cosmosdb-mongodb-api/python/scripts/deploy.sh @@ -62,7 +62,6 @@ az cosmosdb show \ --only-show-errors &>/dev/null if [[ $? != 0 ]]; then - COSMOSDB_ACCOUNT_EXISTED="false" echo "No [$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" echo "Creating [$COSMOSDB_ACCOUNT_NAME] CosmosDB account in the [$RESOURCE_GROUP_NAME] resource group..." @@ -83,7 +82,6 @@ if [[ $? != 0 ]]; then exit 1 fi else - COSMOSDB_ACCOUNT_EXISTED="true" echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already exists in the [$RESOURCE_GROUP_NAME] resource group" fi @@ -107,8 +105,8 @@ fi # --server-version of az cosmosdb create and az cosmosdb update as a ServerVersion property, which the service # ignores, so the account keeps the default server version, 3.6, which the MongoDB driver of the web app refuses. # The script sets the serverVersion property itself, with the API version the Azure CLI uses, and waits until the -# account reports the new server version, which takes about a minute. After upgrading an account that already -# existed, the script also waits with mongosh for the MongoDB endpoint, before it deploys the web app (see below). +# account reports the new server version, which takes about a minute. The MongoDB endpoint can go on serving the old +# version for a while after that: the web app starts without the database and connects once the endpoint switched. echo "Getting [$COSMOSDB_ACCOUNT_NAME] CosmosDB account MongoDB server version in the [$RESOURCE_GROUP_NAME] resource group..." SERVER_VERSION=$(az cosmosdb show \ --name $COSMOSDB_ACCOUNT_NAME \ @@ -125,14 +123,6 @@ else fi if [[ "$SERVER_VERSION" != "$MONGODB_API_VERSION" ]]; then - if [[ "$COSMOSDB_ACCOUNT_EXISTED" == "true" ]]; then - if ! command -v mongosh &>/dev/null; then - echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account runs MongoDB server version [$SERVER_VERSION]: upgrading it to [$MONGODB_API_VERSION] needs mongosh, to wait for its MongoDB endpoint before deploying the web app." - echo "Install mongosh (https://www.mongodb.com/docs/mongodb-shell/install/) and run the script again." - exit 1 - fi - fi - echo "Upgrading [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to MongoDB server version [$MONGODB_API_VERSION]..." az rest \ --method patch \ @@ -167,14 +157,12 @@ if [[ "$SERVER_VERSION" != "$MONGODB_API_VERSION" ]]; then done if [[ "$SERVER_VERSION" == "$MONGODB_API_VERSION" && "$PROVISIONING_STATE" == "Succeeded" ]]; then - COSMOSDB_ACCOUNT_UPGRADED="true" echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account successfully upgraded to MongoDB server version [$MONGODB_API_VERSION]" else echo "Failed to upgrade [$COSMOSDB_ACCOUNT_NAME] CosmosDB account: it still runs MongoDB server version [$SERVER_VERSION]" exit 1 fi else - COSMOSDB_ACCOUNT_UPGRADED="false" echo "[$COSMOSDB_ACCOUNT_NAME] CosmosDB account already runs MongoDB server version [$MONGODB_API_VERSION]" fi @@ -986,42 +974,6 @@ else echo "[$DIAGNOSTIC_SETTINGS_NAME] diagnostic settings for the [$PE_SUBNET_NSG_NAME] network security group for the private endpoint subnet already exist" fi -# An account that already existed before this run can hold the data of an earlier deployment. Microsoft documents that -# an upgrade of the MongoDB server version takes around 15 minutes to complete, and the MongoDB endpoint of such an -# account can go on serving the old version for most of that time, although the account already reports the new one. -# The MongoDB driver of the web app refuses the old version, so a web app deployed in the meantime fails to start. -# After upgrading such an account, the script waits until the endpoint serves at least MongoDB 4.4 (wire version 9), -# which the MongoDB drivers of both web apps accept, before it deploys the web app. -if [[ "$COSMOSDB_ACCOUNT_EXISTED" == "true" && "$COSMOSDB_ACCOUNT_UPGRADED" == "true" ]]; then - if [ -z "$COSMOSDB_CONNECTION_STRING" ]; then - echo "Failed: no connection string to wait for the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account." - exit 1 - fi - - echo "Waiting for the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account to serve MongoDB server version [$MONGODB_API_VERSION]..." - MONGODB_ENDPOINT_READY="false" - - for ATTEMPT in $(seq 1 40); do - mongosh "$COSMOSDB_CONNECTION_STRING" \ - --quiet \ - --eval 'if (db.hello().maxWireVersion < 9) { quit(1) }' &>/dev/null - - if [[ $? == 0 ]]; then - MONGODB_ENDPOINT_READY="true" - break - fi - echo "The MongoDB endpoint does not serve the new MongoDB server version yet (attempt $ATTEMPT of 40)..." - sleep 30 - done - - if [[ "$MONGODB_ENDPOINT_READY" == "true" ]]; then - echo "The MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account serves MongoDB server version [$MONGODB_API_VERSION]" - else - echo "Failed: the MongoDB endpoint of [$COSMOSDB_ACCOUNT_NAME] CosmosDB account does not serve MongoDB server version [$MONGODB_API_VERSION] yet. Run the script again in a few minutes." - exit 1 - fi -fi - # Change current directory to source folder cd "../src" || exit diff --git a/samples/web-app-cosmosdb-mongodb-api/python/src/app.py b/samples/web-app-cosmosdb-mongodb-api/python/src/app.py index aab8e20..ace8a1f 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/src/app.py +++ b/samples/web-app-cosmosdb-mongodb-api/python/src/app.py @@ -2,8 +2,10 @@ import os import datetime import logging +import threading from typing import List, Tuple from flask import Flask, flash, render_template, request, redirect, url_for +from pymongo.errors import PyMongoError from mongodb import MongoDbClient import hashlib @@ -28,12 +30,29 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) -# Global variables for MongoDB configuration -mongodb_client: MongoDbClient +# The MongoDB client is created by the first request that needs it, not when the module is loaded, so the web app +# starts even when the database is not reachable yet, for example while Azure Cosmos DB for MongoDB upgrades the +# server version of the account. A request that finds the database unavailable shows an error, and the next request +# tries again. +mongodb_client: MongoDbClient | None = None +mongodb_client_lock = threading.Lock() +DATABASE_UNAVAILABLE = "The database is not available. Try again in a minute." debug: bool = os.environ.get("FLASK_DEBUG", "false").lower() == "true" activities: List[Tuple[int, str]] = [] + +def get_mongodb_client() -> MongoDbClient | None: + """Return the MongoDB client, creating it on first use, or None while the database is not available.""" + global mongodb_client + with mongodb_client_lock: + if mongodb_client is None: + try: + mongodb_client = MongoDbClient.from_env() + except PyMongoError as e: + logger.error("The database is not available: %s", e) + return mongodb_client + def create_document(activity: str | None = None) -> dict: """Create a document with activity and timestamp.""" @@ -55,13 +74,19 @@ def create_document(activity: str | None = None) -> dict: def read_documents(username: str | None = None) -> List[dict]: """Read all documents from the MongoDB collection.""" documents = [] + activities.clear() + client = get_mongodb_client() + if client is None: + flash(DATABASE_UNAVAILABLE) + return documents try: - if mongodb_client: - activities.clear() - query = {'username': username} if username else {} - for document in mongodb_client.read_documents(query=query): - documents.append(document) - activities.append((document["_id"], document["activity"])) + query = {'username': username} if username else {} + for document in client.read_documents(query=query): + documents.append(document) + activities.append((document["_id"], document["activity"])) + except PyMongoError as e: + logger.error("Error reading documents: %s", e) + flash(DATABASE_UNAVAILABLE) except (ConnectionError, ValueError, KeyError) as e: logger.error("Error reading documents: %s", e) return documents @@ -71,7 +96,10 @@ def index(): """Handle the main page for viewing and adding activities.""" if request.method == 'POST': activity = request.form.get('activity') - if activity: + client = get_mongodb_client() if activity else None + if activity and client is None: + flash(DATABASE_UNAVAILABLE) + elif activity: try: row_id = request.form.get('row_id') if row_id: @@ -79,20 +107,23 @@ def index(): if not row_id.strip(): raise ValueError("Row ID cannot be None or empty") - updated_activity = mongodb_client.update_document_by_id(row_id, {"activity": activity}) + updated_activity = client.update_document_by_id(row_id, {"activity": activity}) if updated_activity: logger.info(f"Activity updated: {row_id}") flash("Activity updated!") else: # Create a document with the activity provided document: dict = create_document(activity) - mongodb_client.insert_document(document) + client.insert_document(document) # Append the activity to the activities list activities.append((document["_id"], activity)) logger.info(f"Activity added: {activity}") flash("Activity added!") + except PyMongoError as e: + logger.error("Error saving the activity: %s", e) + flash(DATABASE_UNAVAILABLE) except (ConnectionError, ValueError) as e: logger.error("Error creating document: %s", e) @@ -112,19 +143,28 @@ def favicon(): def delete(activity_id: int): """Handle deletion of an activity by its index.""" if 0 <= activity_id < len(activities): - # Delete the document from MongoDB - mongodb_client.delete_document_by_id(activities[activity_id][0]) - flash("Activity deleted.") + client = get_mongodb_client() + if client is None: + flash(DATABASE_UNAVAILABLE) + else: + try: + # Delete the document from MongoDB + client.delete_document_by_id(activities[activity_id][0]) + flash("Activity deleted.") + except PyMongoError as e: + logger.error("Error deleting the activity: %s", e) + flash(DATABASE_UNAVAILABLE) return redirect(url_for('index')) # Read debug environment variable debug = os.environ.get("DEBUG", "false").lower() == "true" -# Initialize the application and MongoDB client when the module is loaded. -# This ensures that the setup runs regardless of how the app is started -# (e.g., via 'flask run' or directly). -mongodb_client = MongoDbClient.from_env() +# Validate the configuration when the module is loaded, so that a misconfigured deployment fails at startup, +# regardless of how the app is started (e.g., via 'flask run' or directly). The database is reached later, by the +# first request that needs it (see get_mongodb_client). +if not (os.environ.get("COSMOSDB_CONNECTION_STRING") or os.environ.get("MONGODB_CONNECTION_STRING")): + raise ValueError("Missing required environment variable: COSMOSDB_CONNECTION_STRING or MONGODB_CONNECTION_STRING") # Get username from form or environment variable username = os.environ.get("LOGIN_NAME", "paolo") @@ -133,10 +173,6 @@ def delete(activity_id: int): if not username or not username.strip(): raise ValueError("Username cannot be None or empty") -if mongodb_client: - # Read documents from MongoDB to populate the activities list - read_documents(username) - # Run the Flask application if __name__ == '__main__': app.run(debug=debug) diff --git a/samples/web-app-cosmosdb-mongodb-api/python/src/mongodb.py b/samples/web-app-cosmosdb-mongodb-api/python/src/mongodb.py index 8919bf6..beb5264 100644 --- a/samples/web-app-cosmosdb-mongodb-api/python/src/mongodb.py +++ b/samples/web-app-cosmosdb-mongodb-api/python/src/mongodb.py @@ -78,11 +78,16 @@ def __init__(self, # Connect with connection string self._connect_with_connection_string(connection_string) - if database_name: - self.create_database_if_not_exists(database_name) - if collection_name: - self.create_collection_if_not_exists( - database_name, collection_name) + # Close the connection when the database is not usable, so that a caller that retries does not leak clients + try: + if database_name: + self.create_database_if_not_exists(database_name) + if collection_name: + self.create_collection_if_not_exists( + database_name, collection_name) + except Exception: + self.close() + raise def _connect_with_connection_string(self, connection_string: str): """Connect to MongoDB using connection string."""