diff --git a/CHANGELOG.md b/CHANGELOG.md index e6d279efc9030..e33a8f77e06f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ breaking changes in the upcoming 4.x release. This release is scheduled for **NOTE**: Please refer to the [V3 Migration Guide](/doc/v3-migration-guide.md) for details on updating existing applications using v1.x.y or v2.x.y. +## v3.5.0 - TBD + ## v3.4.0 - 2026-04 ### [Bigtable](/google/cloud/bigtable/README.md) diff --git a/CMakeLists.txt b/CMakeLists.txt index 247831d2f75e1..7ce073fe9de0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,9 +21,9 @@ set(PACKAGE_BUGREPORT "https://github.com/googleapis/google-cloud-cpp/issues") project( google-cloud-cpp - VERSION 3.4.0 + VERSION 3.5.0 LANGUAGES CXX) -set(PROJECT_VERSION_PRE_RELEASE "") +set(PROJECT_VERSION_PRE_RELEASE "rc") if (NOT "${PROJECT_VERSION_PRE_RELEASE}" STREQUAL "") set(PROJECT_VERSION "${PROJECT_VERSION}-${PROJECT_VERSION_PRE_RELEASE}") diff --git a/MODULE.bazel b/MODULE.bazel index 54b3ac4deef55..e97143bc316a4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,7 +16,7 @@ module( name = "google_cloud_cpp", - version = "3.4.0", # Updated by CMake + version = "3.5.0-rc", # Updated by CMake compatibility_level = 3, # Updated by CMake ) diff --git a/ci/abi-dumps/google_cloud_cpp_accessapproval.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_accessapproval.expected.abi.dump.gz index c93aba2bd9715..6736f1d61a94e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_accessapproval.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_accessapproval.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_accesscontextmanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_accesscontextmanager.expected.abi.dump.gz index 1ea276309d829..f8437833053db 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_accesscontextmanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_accesscontextmanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_advisorynotifications.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_advisorynotifications.expected.abi.dump.gz index 0203dd4698158..b20f425295031 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_advisorynotifications.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_advisorynotifications.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_aiplatform.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_aiplatform.expected.abi.dump.gz index 62852ecb99dc6..063277c9ec114 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_aiplatform.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_aiplatform.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_alloydb.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_alloydb.expected.abi.dump.gz index d4c354a209e62..21fef2c500a79 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_alloydb.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_alloydb.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_apigateway.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_apigateway.expected.abi.dump.gz index f9b069b38deb4..fbe94f76dd034 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_apigateway.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_apigateway.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_apigeeconnect.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_apigeeconnect.expected.abi.dump.gz index 2c64c725c63b1..ea6cc7bc14b09 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_apigeeconnect.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_apigeeconnect.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_apikeys.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_apikeys.expected.abi.dump.gz index 1a3245d1898b3..6eab0d8536db3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_apikeys.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_apikeys.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_apiregistry.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_apiregistry.expected.abi.dump.gz index 3af7e12039ff8..8845887554f3a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_apiregistry.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_apiregistry.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_appengine.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_appengine.expected.abi.dump.gz index adb6aaf1f1f05..4ec3c642e8d85 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_appengine.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_appengine.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_apphub.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_apphub.expected.abi.dump.gz index a0272ae8796e3..bbe0c6e837f29 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_apphub.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_apphub.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_artifactregistry.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_artifactregistry.expected.abi.dump.gz index 1a784228233b2..4711938f84ff3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_artifactregistry.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_artifactregistry.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_asset.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_asset.expected.abi.dump.gz index f12001d1c884d..b8b32f74f05e4 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_asset.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_asset.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_assuredworkloads.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_assuredworkloads.expected.abi.dump.gz index f6cdb15aeeecf..faa72ddd0e4d0 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_assuredworkloads.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_assuredworkloads.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_auditmanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_auditmanager.expected.abi.dump.gz index 45cd1c8c5aaf4..0c6412c5b4777 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_auditmanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_auditmanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_automl.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_automl.expected.abi.dump.gz index bc9970b6a3db9..cd353112e562b 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_automl.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_automl.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_backupdr.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_backupdr.expected.abi.dump.gz index 8336ffb145403..ee97b14297168 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_backupdr.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_backupdr.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_baremetalsolution.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_baremetalsolution.expected.abi.dump.gz index 60c59fe38d90f..ef7c1103b431c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_baremetalsolution.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_baremetalsolution.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_batch.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_batch.expected.abi.dump.gz index 7dc302c32cb16..4ee27ae8c167d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_batch.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_batch.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_beyondcorp.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_beyondcorp.expected.abi.dump.gz index 7b5f4fa56c642..859a75a4de12c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_beyondcorp.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_beyondcorp.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_bigquery.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_bigquery.expected.abi.dump.gz index 3ee8b9e729226..a7774f0aed68f 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_bigquery.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_bigquery.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_bigquerycontrol.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_bigquerycontrol.expected.abi.dump.gz index d5ee5602095a3..83cad01e918e4 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_bigquerycontrol.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_bigquerycontrol.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_bigtable.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_bigtable.expected.abi.dump.gz index 75ab84b051b67..169a435f4200c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_bigtable.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_bigtable.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_billing.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_billing.expected.abi.dump.gz index 7c4f1e76b114e..bd555b30041d3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_billing.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_billing.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_binaryauthorization.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_binaryauthorization.expected.abi.dump.gz index ac09732a032e6..baf8ea1cca60c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_binaryauthorization.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_binaryauthorization.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_certificatemanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_certificatemanager.expected.abi.dump.gz index 7fdbb98d4f75d..d0423964d7578 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_certificatemanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_certificatemanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_ces.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_ces.expected.abi.dump.gz index ecddb1c5eea67..d7c8c2c82a206 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_ces.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_ces.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_channel.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_channel.expected.abi.dump.gz index ea93e609077b6..90e08e0384ab4 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_channel.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_channel.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_chronicle.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_chronicle.expected.abi.dump.gz index a3adb75811c0d..652527b0d4588 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_chronicle.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_chronicle.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_cloudbuild.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_cloudbuild.expected.abi.dump.gz index fd1a65c343b68..62f1d5d33d47a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_cloudbuild.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_cloudbuild.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_cloudcontrolspartner.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_cloudcontrolspartner.expected.abi.dump.gz index fd453c75e7c96..f0d0bb3aacb43 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_cloudcontrolspartner.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_cloudcontrolspartner.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_cloudquotas.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_cloudquotas.expected.abi.dump.gz index 0beb6c3c4a414..4a6aa7c439215 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_cloudquotas.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_cloudquotas.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_cloudsecuritycompliance.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_cloudsecuritycompliance.expected.abi.dump.gz index d364370dcf53d..94f0c1c8a9766 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_cloudsecuritycompliance.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_cloudsecuritycompliance.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_commerce.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_commerce.expected.abi.dump.gz index 1952e25dcc177..c90c09c7aab2d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_commerce.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_commerce.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_common.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_common.expected.abi.dump.gz index 5c31dd2fe4c03..50e07e37a2084 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_common.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_common.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_composer.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_composer.expected.abi.dump.gz index 7cb0a85a325d2..444f095005c71 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_composer.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_composer.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_accelerator_types.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_accelerator_types.expected.abi.dump.gz index 98b467bf64d25..6b6800c04c574 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_accelerator_types.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_accelerator_types.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_addresses.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_addresses.expected.abi.dump.gz index e86a571937f0d..bc0b059245a87 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_addresses.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_addresses.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_autoscalers.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_autoscalers.expected.abi.dump.gz index e184512303ea1..523f33152c2b6 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_autoscalers.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_autoscalers.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_backend_buckets.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_backend_buckets.expected.abi.dump.gz index dc73f741d7395..2fd632e010e78 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_backend_buckets.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_backend_buckets.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_backend_services.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_backend_services.expected.abi.dump.gz index 9784dae8dad71..dfb8292c3bdf1 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_backend_services.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_backend_services.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_disk_types.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_disk_types.expected.abi.dump.gz index 30f16afc904e5..2e6a45b30e601 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_disk_types.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_disk_types.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_disks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_disks.expected.abi.dump.gz index c8dca408e495c..035096476b638 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_disks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_disks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_external_vpn_gateways.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_external_vpn_gateways.expected.abi.dump.gz index eb3860de78c6c..78309b5ea85ce 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_external_vpn_gateways.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_external_vpn_gateways.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_firewall_policies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_firewall_policies.expected.abi.dump.gz index 15780287bbf98..4742052ecd246 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_firewall_policies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_firewall_policies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_firewalls.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_firewalls.expected.abi.dump.gz index 320f79c862ff5..43040046e225d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_firewalls.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_firewalls.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_forwarding_rules.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_forwarding_rules.expected.abi.dump.gz index 42177d22e5d4f..0726c1c914aa0 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_forwarding_rules.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_forwarding_rules.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_global_addresses.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_global_addresses.expected.abi.dump.gz index 8041f6c9e0fef..e15e721a41921 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_global_addresses.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_global_addresses.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_global_forwarding_rules.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_global_forwarding_rules.expected.abi.dump.gz index befc4812c4338..4e28eb55ad42c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_global_forwarding_rules.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_global_forwarding_rules.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_global_network_endpoint_groups.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_global_network_endpoint_groups.expected.abi.dump.gz index 2d1c8912470a2..3c7e8d30da965 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_global_network_endpoint_groups.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_global_network_endpoint_groups.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_global_operations.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_global_operations.expected.abi.dump.gz index 435878198c626..3664ff57353be 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_global_operations.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_global_operations.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_global_organization_operations.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_global_organization_operations.expected.abi.dump.gz index c44d3a889578c..fde682cad7b0f 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_global_organization_operations.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_global_organization_operations.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_global_public_delegated_prefixes.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_global_public_delegated_prefixes.expected.abi.dump.gz index 88b72c46dcf43..dfd28afaa7e63 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_global_public_delegated_prefixes.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_global_public_delegated_prefixes.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_health_checks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_health_checks.expected.abi.dump.gz index 9792f1117bf05..1ba21f3d77d8c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_health_checks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_health_checks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_http_health_checks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_http_health_checks.expected.abi.dump.gz index 89332e65fd532..7f2df5b3cae44 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_http_health_checks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_http_health_checks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_https_health_checks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_https_health_checks.expected.abi.dump.gz index 5b77cd4d28342..94d8dbf982833 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_https_health_checks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_https_health_checks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_image_family_views.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_image_family_views.expected.abi.dump.gz index 2c11e4c476c16..5fa2cb4e55b5e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_image_family_views.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_image_family_views.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_images.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_images.expected.abi.dump.gz index ac547c92326e5..a7ffb3d2d9fb4 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_images.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_images.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_instance_group_managers.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_instance_group_managers.expected.abi.dump.gz index 5155d6bb386e9..3d342055f7c62 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_instance_group_managers.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_instance_group_managers.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_instance_groups.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_instance_groups.expected.abi.dump.gz index 7df662d3fcbee..92a5a5c79618c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_instance_groups.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_instance_groups.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_instance_templates.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_instance_templates.expected.abi.dump.gz index 10140c7d409bb..068f5894f98ea 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_instance_templates.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_instance_templates.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_instances.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_instances.expected.abi.dump.gz index 4cc9f7cbc073b..d37525079d8ac 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_instances.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_instances.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_interconnect_attachments.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_interconnect_attachments.expected.abi.dump.gz index 44497e93cc483..04cb6dd409e86 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_interconnect_attachments.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_interconnect_attachments.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_interconnect_locations.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_interconnect_locations.expected.abi.dump.gz index f7a9c4f24642f..f74e691a6dbd1 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_interconnect_locations.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_interconnect_locations.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_interconnects.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_interconnects.expected.abi.dump.gz index f5d6b24b5b607..d1e295489baac 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_interconnects.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_interconnects.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_license_codes.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_license_codes.expected.abi.dump.gz index 2231a75551f51..e3d825e17ac8a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_license_codes.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_license_codes.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_licenses.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_licenses.expected.abi.dump.gz index 5281191157458..066ee71ca95b1 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_licenses.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_licenses.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_machine_images.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_machine_images.expected.abi.dump.gz index f19eca86754a3..dbf45016dbf05 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_machine_images.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_machine_images.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_machine_types.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_machine_types.expected.abi.dump.gz index e073f9ae4a8c5..7b78cfa4ec1c5 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_machine_types.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_machine_types.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_network_attachments.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_network_attachments.expected.abi.dump.gz index 3e49854c88107..cee6ee970ffb3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_network_attachments.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_network_attachments.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_network_edge_security_services.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_network_edge_security_services.expected.abi.dump.gz index a8d8b9d4db74c..d83256fbe38ea 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_network_edge_security_services.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_network_edge_security_services.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_network_endpoint_groups.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_network_endpoint_groups.expected.abi.dump.gz index 9868f5d5f65c8..7e1aa11584aa4 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_network_endpoint_groups.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_network_endpoint_groups.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_network_firewall_policies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_network_firewall_policies.expected.abi.dump.gz index d25c71a776959..d2ff6409af239 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_network_firewall_policies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_network_firewall_policies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_networks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_networks.expected.abi.dump.gz index 3dab36f376e9a..a533b3c9ccb3d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_networks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_networks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_node_groups.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_node_groups.expected.abi.dump.gz index 48b1c2ace388b..fe878468f2b92 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_node_groups.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_node_groups.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_node_templates.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_node_templates.expected.abi.dump.gz index e2d863ee63662..cd6066d7979b2 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_node_templates.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_node_templates.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_node_types.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_node_types.expected.abi.dump.gz index 4ee2a8ad90054..d058d017d5e61 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_node_types.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_node_types.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_packet_mirrorings.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_packet_mirrorings.expected.abi.dump.gz index 88e07f15fee5c..8d5481ca6c952 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_packet_mirrorings.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_packet_mirrorings.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_projects.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_projects.expected.abi.dump.gz index 0a024306af363..c38faa236fb1a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_projects.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_projects.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_public_advertised_prefixes.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_public_advertised_prefixes.expected.abi.dump.gz index 237663e03e03b..36e1db9fb1842 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_public_advertised_prefixes.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_public_advertised_prefixes.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_public_delegated_prefixes.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_public_delegated_prefixes.expected.abi.dump.gz index 4c10ffd99bf01..c23b9a826e9c2 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_public_delegated_prefixes.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_public_delegated_prefixes.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_autoscalers.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_autoscalers.expected.abi.dump.gz index 467cb96c280ad..a046a0072efd8 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_autoscalers.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_autoscalers.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_backend_services.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_backend_services.expected.abi.dump.gz index 413f4a62fa446..4e58714938275 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_backend_services.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_backend_services.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_commitments.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_commitments.expected.abi.dump.gz index ddac89445efef..7194417c4e76e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_commitments.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_commitments.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_disk_types.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_disk_types.expected.abi.dump.gz index 942512b116ea7..012aa9c3f560a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_disk_types.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_disk_types.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_disks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_disks.expected.abi.dump.gz index 16c4562bf2d2c..b9df5b29d52ab 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_disks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_disks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_health_check_services.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_health_check_services.expected.abi.dump.gz index d2b0bc0ed1c37..5430c2d364f4c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_health_check_services.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_health_check_services.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_health_checks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_health_checks.expected.abi.dump.gz index d033e89d8f30f..bf95c590b5737 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_health_checks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_health_checks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_instance_group_managers.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_instance_group_managers.expected.abi.dump.gz index e4aa1041033f1..fbf42dc80071b 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_instance_group_managers.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_instance_group_managers.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_instance_groups.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_instance_groups.expected.abi.dump.gz index d89288927f679..5282711ddf51d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_instance_groups.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_instance_groups.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_instance_templates.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_instance_templates.expected.abi.dump.gz index 428ab234a5aac..57714fbe9e5b4 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_instance_templates.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_instance_templates.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_instances.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_instances.expected.abi.dump.gz index 572f50c10ff28..fe01d8f8a1214 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_instances.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_instances.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_network_endpoint_groups.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_network_endpoint_groups.expected.abi.dump.gz index 9fa18093eda02..ee5f165e08ce6 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_network_endpoint_groups.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_network_endpoint_groups.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_network_firewall_policies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_network_firewall_policies.expected.abi.dump.gz index d64411a4e17db..56f0a73fd37ea 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_network_firewall_policies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_network_firewall_policies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_notification_endpoints.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_notification_endpoints.expected.abi.dump.gz index 62b06ace5958c..27c60a32c5549 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_notification_endpoints.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_notification_endpoints.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_operations.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_operations.expected.abi.dump.gz index 3bb7a2fc41a35..ca8dde80be4d9 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_operations.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_operations.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_security_policies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_security_policies.expected.abi.dump.gz index 821fed1d35291..8076cc0b4105e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_security_policies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_security_policies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_region_ssl_certificates.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_region_ssl_certificates.expected.abi.dump.gz index 54c1f004ca002..075ce1887ae45 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_region_ssl_certificates.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_region_ssl_certificates.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_ssl_policies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_ssl_policies.expected.abi.dump.gz index bee0ed8be71db..95b2262f6e3e7 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_ssl_policies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_ssl_policies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_subnetworks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_subnetworks.expected.abi.dump.gz index b6113abe9e1f0..a55f513804119 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_subnetworks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_subnetworks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_target_grpc_proxies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_target_grpc_proxies.expected.abi.dump.gz index 5398241f32c49..87eb5906afc28 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_target_grpc_proxies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_target_grpc_proxies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_target_http_proxies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_target_http_proxies.expected.abi.dump.gz index 3acf1930e8bac..93b94f31b753b 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_target_http_proxies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_target_http_proxies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_target_https_proxies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_target_https_proxies.expected.abi.dump.gz index daedd49218f73..7eb3fe5de8b7e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_target_https_proxies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_target_https_proxies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_target_instances.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_target_instances.expected.abi.dump.gz index b96887578c026..ff91b0392291f 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_target_instances.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_target_instances.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_target_pools.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_target_pools.expected.abi.dump.gz index beceec6234fa3..df753fb57330a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_target_pools.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_target_pools.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_target_ssl_proxies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_target_ssl_proxies.expected.abi.dump.gz index 453b2ba8952e4..ec75f806bd9a2 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_target_ssl_proxies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_target_ssl_proxies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_target_tcp_proxies.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_target_tcp_proxies.expected.abi.dump.gz index 9e6e0cbc5389d..cb2300e70bf14 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_target_tcp_proxies.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_target_tcp_proxies.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_target_vpn_gateways.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_target_vpn_gateways.expected.abi.dump.gz index 3ca95b275fabd..62fd92effb09d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_target_vpn_gateways.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_target_vpn_gateways.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_url_maps.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_url_maps.expected.abi.dump.gz index aa0c8fcf57d06..a3a7ccd1f4d5a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_url_maps.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_url_maps.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_vpn_gateways.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_vpn_gateways.expected.abi.dump.gz index 28e0fc02bf931..5c2e3282c4def 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_vpn_gateways.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_vpn_gateways.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_vpn_tunnels.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_vpn_tunnels.expected.abi.dump.gz index d23b8e8e2c1ce..cdcce94e59a65 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_vpn_tunnels.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_vpn_tunnels.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_zone_operations.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_zone_operations.expected.abi.dump.gz index 9e2d16e9544f0..0908a4528b2fb 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_zone_operations.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_zone_operations.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_compute_zones.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_compute_zones.expected.abi.dump.gz index 5a909db0f43d1..c753da4f85061 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_compute_zones.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_compute_zones.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_confidentialcomputing.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_confidentialcomputing.expected.abi.dump.gz index db31eeb932938..92ad01786fcb6 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_confidentialcomputing.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_confidentialcomputing.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_config.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_config.expected.abi.dump.gz index 359572932a463..5353f1dfb2a47 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_config.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_config.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_configdelivery.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_configdelivery.expected.abi.dump.gz index aaafa8ea1e97c..ebca496119fef 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_configdelivery.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_configdelivery.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_connectors.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_connectors.expected.abi.dump.gz index 417ef3b1b74a9..2c0e6acac0f87 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_connectors.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_connectors.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_contactcenterinsights.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_contactcenterinsights.expected.abi.dump.gz index c7c63f806ecde..52f31ffea1977 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_contactcenterinsights.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_contactcenterinsights.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_container.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_container.expected.abi.dump.gz index 0effdb9865fe6..ed48353b1ef2e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_container.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_container.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_containeranalysis.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_containeranalysis.expected.abi.dump.gz index e7105b05be655..08eb5cd39f3a3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_containeranalysis.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_containeranalysis.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_contentwarehouse.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_contentwarehouse.expected.abi.dump.gz index a8b243ce3572b..7667f54b817cf 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_contentwarehouse.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_contentwarehouse.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_datacatalog.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_datacatalog.expected.abi.dump.gz index 5007590e26555..3ada95e858650 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_datacatalog.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_datacatalog.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_dataform.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_dataform.expected.abi.dump.gz index c925475c9d557..fc16867068c70 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_dataform.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_dataform.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_datafusion.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_datafusion.expected.abi.dump.gz index fe675a7bc7ace..851ebaf27cc75 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_datafusion.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_datafusion.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_datamigration.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_datamigration.expected.abi.dump.gz index da14fab748f24..22334aceb3e1e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_datamigration.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_datamigration.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_dataplex.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_dataplex.expected.abi.dump.gz index 6d5c6f86cef5a..6755d126681ce 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_dataplex.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_dataplex.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_dataproc.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_dataproc.expected.abi.dump.gz index 27415636e04f1..201928faeec55 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_dataproc.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_dataproc.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_datastore.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_datastore.expected.abi.dump.gz index 342c395fae6cc..794bd3bc20876 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_datastore.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_datastore.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_datastream.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_datastream.expected.abi.dump.gz index cf098e678c0db..d57de681cd1cb 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_datastream.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_datastream.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_deploy.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_deploy.expected.abi.dump.gz index 4a028f66a65ba..4037bb969693b 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_deploy.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_deploy.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_developerconnect.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_developerconnect.expected.abi.dump.gz index cfa6b9651f5b3..f72512585a651 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_developerconnect.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_developerconnect.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_devicestreaming.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_devicestreaming.expected.abi.dump.gz index 10dc59323f3af..62d71dfc69fb3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_devicestreaming.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_devicestreaming.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_dialogflow_cx.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_dialogflow_cx.expected.abi.dump.gz index 7c8aad7e02a52..b570666deb6de 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_dialogflow_cx.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_dialogflow_cx.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_dialogflow_es.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_dialogflow_es.expected.abi.dump.gz index 63d3c7b42e42b..ee8dfc38ef26a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_dialogflow_es.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_dialogflow_es.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_discoveryengine.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_discoveryengine.expected.abi.dump.gz index 5be3259e3afd7..77e262382852d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_discoveryengine.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_discoveryengine.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_dlp.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_dlp.expected.abi.dump.gz index 9129a385f36cd..a2298ff64ea2f 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_dlp.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_dlp.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_documentai.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_documentai.expected.abi.dump.gz index 57b4302891b6d..cd19ec8624925 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_documentai.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_documentai.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_domains.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_domains.expected.abi.dump.gz index 14420c28702bf..2141cb2290904 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_domains.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_domains.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_edgecontainer.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_edgecontainer.expected.abi.dump.gz index 6d169daba580f..e388f2fbc836c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_edgecontainer.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_edgecontainer.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_edgenetwork.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_edgenetwork.expected.abi.dump.gz index ea6ac42f0af16..459c52c320e60 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_edgenetwork.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_edgenetwork.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_essentialcontacts.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_essentialcontacts.expected.abi.dump.gz index 1a0da77a79921..792215f038249 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_essentialcontacts.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_essentialcontacts.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_eventarc.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_eventarc.expected.abi.dump.gz index 442daf5dd9d87..80b75d5c7c069 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_eventarc.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_eventarc.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_filestore.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_filestore.expected.abi.dump.gz index b34c33e8bf61a..f3abd0b3b5b27 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_filestore.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_filestore.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_financialservices.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_financialservices.expected.abi.dump.gz index 7a74e90f45575..1685629f73486 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_financialservices.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_financialservices.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_functions.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_functions.expected.abi.dump.gz index b5c9c7dac82cf..b8efbc718b989 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_functions.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_functions.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_gkebackup.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_gkebackup.expected.abi.dump.gz index d589553a6df99..4bc89a39bdcd2 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_gkebackup.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_gkebackup.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_gkeconnect.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_gkeconnect.expected.abi.dump.gz index b6900d14f75e2..055a78a989113 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_gkeconnect.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_gkeconnect.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_gkehub.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_gkehub.expected.abi.dump.gz index 8447b31d17503..eb8e0fb171d00 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_gkehub.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_gkehub.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_gkemulticloud.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_gkemulticloud.expected.abi.dump.gz index 7917d981f3d10..407cfd615c344 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_gkemulticloud.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_gkemulticloud.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_gkerecommender.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_gkerecommender.expected.abi.dump.gz index ff9a2f97735af..ab3a2ae8f962e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_gkerecommender.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_gkerecommender.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_grpc_utils.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_grpc_utils.expected.abi.dump.gz index fc23abec6a9e7..cce82d7c1d968 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_grpc_utils.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_grpc_utils.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_hypercomputecluster.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_hypercomputecluster.expected.abi.dump.gz index 3cda1358a6919..ca46cf2fbbcbc 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_hypercomputecluster.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_hypercomputecluster.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_iam.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_iam.expected.abi.dump.gz index 3b49503020c95..cb5535dcd4b45 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_iam.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_iam.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_iap.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_iap.expected.abi.dump.gz index 90e96454810c9..98119be572ade 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_iap.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_iap.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_ids.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_ids.expected.abi.dump.gz index 497a8e3c67b97..9216792858e67 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_ids.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_ids.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_kms.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_kms.expected.abi.dump.gz index 331132a060993..f905fabeccfb8 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_kms.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_kms.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_language.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_language.expected.abi.dump.gz index 4c3e4110739fd..09a55d62e39fc 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_language.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_language.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_licensemanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_licensemanager.expected.abi.dump.gz index e6578a4352e88..3f11b1fb7f8ef 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_licensemanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_licensemanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_logging.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_logging.expected.abi.dump.gz index 76456d39a09eb..3832311947af0 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_logging.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_logging.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_lustre.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_lustre.expected.abi.dump.gz index d399e2410d58e..a19b046a01963 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_lustre.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_lustre.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_maintenance.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_maintenance.expected.abi.dump.gz index 2b403ab5fabc8..c15e14067fe3a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_maintenance.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_maintenance.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_managedidentities.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_managedidentities.expected.abi.dump.gz index f7f1c86db25b9..7748b59da2ab6 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_managedidentities.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_managedidentities.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_managedkafka.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_managedkafka.expected.abi.dump.gz index 68c73f4b9d7fb..b49b5b67f0ef7 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_managedkafka.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_managedkafka.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_memcache.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_memcache.expected.abi.dump.gz index c21c177ba2200..9e51683abbc2b 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_memcache.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_memcache.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_memorystore.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_memorystore.expected.abi.dump.gz index 494c7b9ebce9d..06e5cb63048fb 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_memorystore.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_memorystore.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_metastore.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_metastore.expected.abi.dump.gz index deb3d85f7f698..b5292a9cf0192 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_metastore.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_metastore.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_migrationcenter.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_migrationcenter.expected.abi.dump.gz index aaed628f09d66..e73c9822cfeff 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_migrationcenter.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_migrationcenter.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_monitoring.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_monitoring.expected.abi.dump.gz index 1d862d620b7d9..9e65dee453db5 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_monitoring.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_monitoring.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_netapp.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_netapp.expected.abi.dump.gz index 149d53ba2ad12..8a3bba4cdf9cf 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_netapp.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_netapp.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_networkconnectivity.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_networkconnectivity.expected.abi.dump.gz index d45aa0838d8dc..c77e15f6a5aa4 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_networkconnectivity.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_networkconnectivity.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_networkmanagement.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_networkmanagement.expected.abi.dump.gz index aa1cc6e0eb38a..bd8ec003783e7 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_networkmanagement.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_networkmanagement.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_networksecurity.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_networksecurity.expected.abi.dump.gz index c265adfb780d7..13a30c650399e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_networksecurity.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_networksecurity.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_networkservices.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_networkservices.expected.abi.dump.gz index 4c4d1d5119c75..562e4c9650124 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_networkservices.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_networkservices.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_notebooks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_notebooks.expected.abi.dump.gz index 6fa9b5251e24d..ea8c24fa2f8ea 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_notebooks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_notebooks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_oauth2.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_oauth2.expected.abi.dump.gz index a05e2fba9b55f..a70329ccefbe1 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_oauth2.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_oauth2.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_opentelemetry.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_opentelemetry.expected.abi.dump.gz index 7f400252e8823..2955a64473aa8 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_opentelemetry.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_opentelemetry.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_optimization.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_optimization.expected.abi.dump.gz index 41101c27a4829..cd35f04292c7c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_optimization.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_optimization.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_oracledatabase.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_oracledatabase.expected.abi.dump.gz index 8e800f6d64c07..bd6cc1c3aa339 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_oracledatabase.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_oracledatabase.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_orgpolicy.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_orgpolicy.expected.abi.dump.gz index 078f0de05d411..949bfa2b52719 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_orgpolicy.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_orgpolicy.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_osconfig.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_osconfig.expected.abi.dump.gz index ea5a9d61f74da..53431d6df9476 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_osconfig.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_osconfig.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_oslogin.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_oslogin.expected.abi.dump.gz index 77032e3caf6e9..efe9371ac2a4a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_oslogin.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_oslogin.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_parallelstore.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_parallelstore.expected.abi.dump.gz index f04b31e125b6e..d284247895d79 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_parallelstore.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_parallelstore.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_parametermanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_parametermanager.expected.abi.dump.gz index 2f5a7bf35d60c..644ce8bdea53e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_parametermanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_parametermanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_policysimulator.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_policysimulator.expected.abi.dump.gz index ef28526200044..d47085a25b6c3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_policysimulator.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_policysimulator.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_policytroubleshooter.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_policytroubleshooter.expected.abi.dump.gz index 9093342df3253..1da3be44060fe 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_policytroubleshooter.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_policytroubleshooter.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_privateca.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_privateca.expected.abi.dump.gz index 0365bcf1ae295..1ca3b03d220e0 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_privateca.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_privateca.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_privilegedaccessmanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_privilegedaccessmanager.expected.abi.dump.gz index 5f0544f2cedb4..577904e8a03b4 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_privilegedaccessmanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_privilegedaccessmanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_profiler.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_profiler.expected.abi.dump.gz index 540a384b1b05b..a98e55239e83b 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_profiler.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_profiler.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_publicca.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_publicca.expected.abi.dump.gz index c11f230c8de90..0a90f00b3d5fb 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_publicca.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_publicca.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_pubsub.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_pubsub.expected.abi.dump.gz index 9e0c07725ce59..c748b548018e3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_pubsub.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_pubsub.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_rapidmigrationassessment.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_rapidmigrationassessment.expected.abi.dump.gz index d2fb099a728d3..bedefa95e4d7a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_rapidmigrationassessment.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_rapidmigrationassessment.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_recaptchaenterprise.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_recaptchaenterprise.expected.abi.dump.gz index 753319b639a2f..312b4be219ff9 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_recaptchaenterprise.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_recaptchaenterprise.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_recommender.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_recommender.expected.abi.dump.gz index 4881938b08dab..49fdeab47ec9a 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_recommender.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_recommender.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_redis.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_redis.expected.abi.dump.gz index 417d5f9f6176b..4ec5c59f15d55 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_redis.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_redis.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_resourcemanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_resourcemanager.expected.abi.dump.gz index 4a55fdd2d9548..ebe75a51afd86 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_resourcemanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_resourcemanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_retail.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_retail.expected.abi.dump.gz index d57bd3cd451e8..f1d8730395ba0 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_retail.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_retail.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_run.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_run.expected.abi.dump.gz index 9c1cf43d2ccac..8a038bc9939c2 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_run.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_run.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_scheduler.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_scheduler.expected.abi.dump.gz index 4f1f57f475442..354125d05e8b0 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_scheduler.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_scheduler.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_secretmanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_secretmanager.expected.abi.dump.gz index 38999c5262f2b..52286853077a5 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_secretmanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_secretmanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_securesourcemanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_securesourcemanager.expected.abi.dump.gz index 2ab80bae50546..2bd0a271f2157 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_securesourcemanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_securesourcemanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_securitycenter.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_securitycenter.expected.abi.dump.gz index cc8a979e16e50..62cec308b98b9 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_securitycenter.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_securitycenter.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_securitycentermanagement.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_securitycentermanagement.expected.abi.dump.gz index 6a896cbfc2c55..736c54408e505 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_securitycentermanagement.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_securitycentermanagement.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_servicecontrol.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_servicecontrol.expected.abi.dump.gz index 9d711e279c21a..ad8ce9eb8d530 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_servicecontrol.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_servicecontrol.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_servicedirectory.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_servicedirectory.expected.abi.dump.gz index 270b9f1442cd3..53c2b95b61f65 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_servicedirectory.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_servicedirectory.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_servicehealth.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_servicehealth.expected.abi.dump.gz index 56a938fd3baee..4b40d86b4fd26 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_servicehealth.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_servicehealth.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_servicemanagement.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_servicemanagement.expected.abi.dump.gz index e03445589145b..7a3a73105c60d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_servicemanagement.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_servicemanagement.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_serviceusage.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_serviceusage.expected.abi.dump.gz index 1cdeae311d07e..0761ba34374c9 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_serviceusage.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_serviceusage.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_shell.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_shell.expected.abi.dump.gz index 3e619497cc233..fd4a3bc2069de 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_shell.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_shell.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_spanner.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_spanner.expected.abi.dump.gz index 8cadeb3613730..d53552f654bc6 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_spanner.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_spanner.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_speech.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_speech.expected.abi.dump.gz index c61bfc42f6d89..f66849e7ea40b 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_speech.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_speech.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_sql.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_sql.expected.abi.dump.gz index 1596a0bc2111d..9ffaa0a54e87e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_sql.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_sql.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz index 88d8d8e92b605..d6d6234f11cac 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_storage.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_storage_grpc.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_storage_grpc.expected.abi.dump.gz index 35e5176e497ad..b13c42e412e07 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_storage_grpc.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_storage_grpc.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_storagebatchoperations.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_storagebatchoperations.expected.abi.dump.gz index 987e2ee053656..f3882082380d9 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_storagebatchoperations.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_storagebatchoperations.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_storagecontrol.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_storagecontrol.expected.abi.dump.gz index 95c6e6f9ad968..7007bfd069f2b 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_storagecontrol.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_storagecontrol.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_storageinsights.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_storageinsights.expected.abi.dump.gz index aa76382113450..de650021a2221 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_storageinsights.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_storageinsights.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_storagetransfer.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_storagetransfer.expected.abi.dump.gz index 6ab6035aff535..bc3bfdd362b4d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_storagetransfer.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_storagetransfer.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_support.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_support.expected.abi.dump.gz index 8cb3b8eb27f98..86f86963f4c6f 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_support.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_support.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_talent.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_talent.expected.abi.dump.gz index 9ac78237f2d3c..4a6ba507e02ce 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_talent.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_talent.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_tasks.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_tasks.expected.abi.dump.gz index dc14bcd2b04f2..a5777fc8dcbe1 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_tasks.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_tasks.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_telcoautomation.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_telcoautomation.expected.abi.dump.gz index ad2657043c91f..69e098c25f7e9 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_telcoautomation.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_telcoautomation.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_texttospeech.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_texttospeech.expected.abi.dump.gz index 07e41070bfe1f..b925139df52a5 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_texttospeech.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_texttospeech.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_timeseriesinsights.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_timeseriesinsights.expected.abi.dump.gz index a71cf6b3c094f..edc25a1364572 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_timeseriesinsights.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_timeseriesinsights.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_tpu.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_tpu.expected.abi.dump.gz index 0a301b0d0769c..6224cea08de81 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_tpu.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_tpu.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_trace.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_trace.expected.abi.dump.gz index 800b277e78dfc..7ac3e7e8d859d 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_trace.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_trace.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_translate.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_translate.expected.abi.dump.gz index 7ebe2f6af8a92..d338e4ace299e 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_translate.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_translate.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_universe_domain.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_universe_domain.expected.abi.dump.gz index e2816f824c3f2..6b5e0b60b55dc 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_universe_domain.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_universe_domain.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_vectorsearch.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_vectorsearch.expected.abi.dump.gz index 8895aab5b67f4..84a6e82c3891c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_vectorsearch.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_vectorsearch.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_video.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_video.expected.abi.dump.gz index 7ff238738898a..0b8f3669e39a3 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_video.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_video.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_videointelligence.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_videointelligence.expected.abi.dump.gz index 9a385baf8a68a..bd0039a8f301c 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_videointelligence.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_videointelligence.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_vision.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_vision.expected.abi.dump.gz index c3c8fb9e915b5..4b90dea0fad4f 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_vision.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_vision.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_visionai.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_visionai.expected.abi.dump.gz index 9618e3301a977..4e929b0c89e48 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_visionai.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_visionai.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_vmmigration.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_vmmigration.expected.abi.dump.gz index c08e57da831a3..7a4bdb5fc9bed 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_vmmigration.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_vmmigration.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_vmwareengine.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_vmwareengine.expected.abi.dump.gz index 6fb722facb88e..3c2872ae20782 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_vmwareengine.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_vmwareengine.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_vpcaccess.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_vpcaccess.expected.abi.dump.gz index 985bb6d9a75c1..7fb6049025969 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_vpcaccess.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_vpcaccess.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_webrisk.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_webrisk.expected.abi.dump.gz index 4d403f0d4e62f..31232da8051be 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_webrisk.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_webrisk.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_websecurityscanner.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_websecurityscanner.expected.abi.dump.gz index 51f28d9b76d63..c9c5192c3ccb2 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_websecurityscanner.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_websecurityscanner.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_workflows.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_workflows.expected.abi.dump.gz index 0002ec4183bb8..cd8f90026be13 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_workflows.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_workflows.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_workloadmanager.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_workloadmanager.expected.abi.dump.gz index e4fbb816a44b1..d9ca667534c0f 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_workloadmanager.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_workloadmanager.expected.abi.dump.gz differ diff --git a/ci/abi-dumps/google_cloud_cpp_workstations.expected.abi.dump.gz b/ci/abi-dumps/google_cloud_cpp_workstations.expected.abi.dump.gz index b0041c04a84d4..4b662918b14cb 100644 Binary files a/ci/abi-dumps/google_cloud_cpp_workstations.expected.abi.dump.gz and b/ci/abi-dumps/google_cloud_cpp_workstations.expected.abi.dump.gz differ diff --git a/google/cloud/BUILD.bazel b/google/cloud/BUILD.bazel index dd4feee33dcf0..708169ddda3d5 100644 --- a/google/cloud/BUILD.bazel +++ b/google/cloud/BUILD.bazel @@ -237,6 +237,8 @@ cc_library( name = "google_cloud_cpp_rest_internal", srcs = google_cloud_cpp_rest_internal_srcs, hdrs = google_cloud_cpp_rest_internal_hdrs, + # TODO(#16079): Remove macro definition when GA. + cxxopts = ["-DGOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB"], linkopts = select({ "@platforms//os:windows": [ "-DEFAULTLIB:bcrypt.lib", @@ -273,6 +275,8 @@ cc_library( [cc_test( name = test.replace("/", "_").replace(".cc", ""), srcs = [test], + # TODO(#16079): Remove macro definition when GA. + cxxopts = ["-DGOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB"], deps = [ ":google_cloud_cpp_rest_internal", "//google/cloud/testing_util:google_cloud_cpp_testing_private", diff --git a/google/cloud/google_cloud_cpp_common.bzl b/google/cloud/google_cloud_cpp_common.bzl index 450a070542af9..421c9c00a4160 100644 --- a/google/cloud/google_cloud_cpp_common.bzl +++ b/google/cloud/google_cloud_cpp_common.bzl @@ -74,6 +74,7 @@ google_cloud_cpp_common_hdrs = [ "internal/retry_info.h", "internal/retry_loop_helpers.h", "internal/retry_policy_impl.h", + "internal/run_async_base.h", "internal/service_endpoint.h", "internal/sha256_hash.h", "internal/sha256_hmac.h", diff --git a/google/cloud/google_cloud_cpp_common.cmake b/google/cloud/google_cloud_cpp_common.cmake index a3f11bb04ac9c..8b3601db39f01 100644 --- a/google/cloud/google_cloud_cpp_common.cmake +++ b/google/cloud/google_cloud_cpp_common.cmake @@ -117,6 +117,7 @@ add_library( internal/retry_loop_helpers.h internal/retry_policy_impl.cc internal/retry_policy_impl.h + internal/run_async_base.h internal/service_endpoint.cc internal/service_endpoint.h internal/sha256_hash.cc diff --git a/google/cloud/google_cloud_cpp_rest_internal.bzl b/google/cloud/google_cloud_cpp_rest_internal.bzl index 1506bb22a18cd..81603086f1e8e 100644 --- a/google/cloud/google_cloud_cpp_rest_internal.bzl +++ b/google/cloud/google_cloud_cpp_rest_internal.bzl @@ -54,6 +54,7 @@ google_cloud_cpp_rest_internal_hdrs = [ "internal/oauth2_logging_credentials.h", "internal/oauth2_minimal_iam_credentials_rest.h", "internal/oauth2_refreshing_credentials_wrapper.h", + "internal/oauth2_regional_access_boundary_token_manager.h", "internal/oauth2_service_account_credentials.h", "internal/oauth2_universe_domain.h", "internal/parse_service_account_p12_file.h", @@ -65,6 +66,8 @@ google_cloud_cpp_rest_internal_hdrs = [ "internal/rest_opentelemetry.h", "internal/rest_options.h", "internal/rest_parse_json_error.h", + "internal/rest_pure_background_threads_impl.h", + "internal/rest_pure_completion_queue_impl.h", "internal/rest_request.h", "internal/rest_response.h", "internal/rest_retry_loop.h", @@ -111,6 +114,7 @@ google_cloud_cpp_rest_internal_srcs = [ "internal/oauth2_logging_credentials.cc", "internal/oauth2_minimal_iam_credentials_rest.cc", "internal/oauth2_refreshing_credentials_wrapper.cc", + "internal/oauth2_regional_access_boundary_token_manager.cc", "internal/oauth2_service_account_credentials.cc", "internal/oauth2_universe_domain.cc", "internal/openssl/parse_service_account_p12_file.cc", @@ -121,6 +125,8 @@ google_cloud_cpp_rest_internal_srcs = [ "internal/rest_lro_helpers.cc", "internal/rest_opentelemetry.cc", "internal/rest_parse_json_error.cc", + "internal/rest_pure_background_threads_impl.cc", + "internal/rest_pure_completion_queue_impl.cc", "internal/rest_request.cc", "internal/rest_response.cc", "internal/rest_set_metadata.cc", diff --git a/google/cloud/google_cloud_cpp_rest_internal.cmake b/google/cloud/google_cloud_cpp_rest_internal.cmake index a6d611cbb8ca7..41926d4906226 100644 --- a/google/cloud/google_cloud_cpp_rest_internal.cmake +++ b/google/cloud/google_cloud_cpp_rest_internal.cmake @@ -92,6 +92,8 @@ add_library( internal/oauth2_minimal_iam_credentials_rest.h internal/oauth2_refreshing_credentials_wrapper.cc internal/oauth2_refreshing_credentials_wrapper.h + internal/oauth2_regional_access_boundary_token_manager.cc + internal/oauth2_regional_access_boundary_token_manager.h internal/oauth2_service_account_credentials.cc internal/oauth2_service_account_credentials.h internal/oauth2_universe_domain.cc @@ -113,6 +115,10 @@ add_library( internal/rest_options.h internal/rest_parse_json_error.cc internal/rest_parse_json_error.h + internal/rest_pure_background_threads_impl.cc + internal/rest_pure_background_threads_impl.h + internal/rest_pure_completion_queue_impl.cc + internal/rest_pure_completion_queue_impl.h internal/rest_request.cc internal/rest_request.h internal/rest_response.cc @@ -280,6 +286,7 @@ if (BUILD_TESTING) internal/oauth2_logging_credentials_test.cc internal/oauth2_minimal_iam_credentials_rest_test.cc internal/oauth2_refreshing_credentials_wrapper_test.cc + internal/oauth2_regional_access_boundary_token_manager_test.cc internal/oauth2_service_account_credentials_test.cc internal/oauth2_universe_domain_test.cc internal/populate_rest_options_test.cc diff --git a/google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl b/google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl index b80f9ce502207..97315183034dc 100644 --- a/google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl +++ b/google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl @@ -51,6 +51,7 @@ google_cloud_cpp_rest_internal_unit_tests = [ "internal/oauth2_logging_credentials_test.cc", "internal/oauth2_minimal_iam_credentials_rest_test.cc", "internal/oauth2_refreshing_credentials_wrapper_test.cc", + "internal/oauth2_regional_access_boundary_token_manager_test.cc", "internal/oauth2_service_account_credentials_test.cc", "internal/oauth2_universe_domain_test.cc", "internal/populate_rest_options_test.cc", diff --git a/google/cloud/internal/completion_queue_impl.h b/google/cloud/internal/completion_queue_impl.h index 31afec8e3c005..3f623668aa408 100644 --- a/google/cloud/internal/completion_queue_impl.h +++ b/google/cloud/internal/completion_queue_impl.h @@ -18,6 +18,7 @@ #include "google/cloud/async_operation.h" #include "google/cloud/future.h" #include "google/cloud/internal/invoke_result.h" +#include "google/cloud/internal/run_async_base.h" #include "google/cloud/internal/throw_delegate.h" #include "google/cloud/status_or.h" #include "google/cloud/version.h" @@ -30,12 +31,6 @@ namespace cloud { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN namespace internal { -// Type erase the callables in RunAsync() -struct RunAsyncBase { - virtual ~RunAsyncBase() = default; - virtual void exec() = 0; -}; - /** * The implementation details for `CompletionQueue`. * diff --git a/google/cloud/internal/oauth2_api_key_credentials.cc b/google/cloud/internal/oauth2_api_key_credentials.cc index aee0f3386f16c..7e8b975cc9b29 100644 --- a/google/cloud/internal/oauth2_api_key_credentials.cc +++ b/google/cloud/internal/oauth2_api_key_credentials.cc @@ -27,12 +27,9 @@ StatusOr ApiKeyCredentials::GetToken( return AccessToken{std::string{}, tp}; } -StatusOr> -ApiKeyCredentials::AuthenticationHeaders(std::chrono::system_clock::time_point, - std::string_view) { - std::vector headers; - headers.emplace_back("x-goog-api-key", api_key_); - return headers; +StatusOr ApiKeyCredentials::Authorization( + std::chrono::system_clock::time_point) { + return rest_internal::HttpHeader{"x-goog-api-key", api_key_}; } GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/internal/oauth2_api_key_credentials.h b/google/cloud/internal/oauth2_api_key_credentials.h index 1ab7141d46ece..16258d9e3d753 100644 --- a/google/cloud/internal/oauth2_api_key_credentials.h +++ b/google/cloud/internal/oauth2_api_key_credentials.h @@ -37,9 +37,8 @@ class ApiKeyCredentials : public oauth2_internal::Credentials { StatusOr GetToken( std::chrono::system_clock::time_point tp) override; - StatusOr> AuthenticationHeaders( - std::chrono::system_clock::time_point, - std::string_view endpoint) override; + StatusOr Authorization( + std::chrono::system_clock::time_point tp) override; private: std::string api_key_; diff --git a/google/cloud/internal/oauth2_cached_credentials.cc b/google/cloud/internal/oauth2_cached_credentials.cc index 8ae00341c43df..11d81c5fdf18f 100644 --- a/google/cloud/internal/oauth2_cached_credentials.cc +++ b/google/cloud/internal/oauth2_cached_credentials.cc @@ -83,6 +83,11 @@ StatusOr CachedCredentials::project_id( return impl_->project_id(options); } +Credentials::AllowedLocationsRequestType +CachedCredentials::AllowedLocationsRequest() const { + return impl_->AllowedLocationsRequest(); +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace oauth2_internal } // namespace cloud diff --git a/google/cloud/internal/oauth2_cached_credentials.h b/google/cloud/internal/oauth2_cached_credentials.h index 9cd0bd528db96..ee8e18e9eb8ba 100644 --- a/google/cloud/internal/oauth2_cached_credentials.h +++ b/google/cloud/internal/oauth2_cached_credentials.h @@ -54,6 +54,8 @@ class CachedCredentials : public Credentials { StatusOr universe_domain(Options const& options) const override; StatusOr project_id() const override; StatusOr project_id(Options const& options) const override; + Credentials::AllowedLocationsRequestType AllowedLocationsRequest() + const override; private: std::shared_ptr impl_; diff --git a/google/cloud/internal/oauth2_compute_engine_credentials.cc b/google/cloud/internal/oauth2_compute_engine_credentials.cc index 4113e07fb3355..ac9cb5d2175f5 100644 --- a/google/cloud/internal/oauth2_compute_engine_credentials.cc +++ b/google/cloud/internal/oauth2_compute_engine_credentials.cc @@ -239,6 +239,16 @@ StatusOr ComputeEngineCredentials::project_id( return RetrieveProjectId(lk, options); } +Credentials::AllowedLocationsRequestType +ComputeEngineCredentials::AllowedLocationsRequest() const { + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + return ServiceAccountAllowedLocationsRequest{AccountEmail()}; +#else + return std::monostate{}; +#endif +} + StatusOr ComputeEngineCredentials::RetrieveUniverseDomain( std::lock_guard const&, Options const& options) const { // Fetch the universe domain only once. diff --git a/google/cloud/internal/oauth2_compute_engine_credentials.h b/google/cloud/internal/oauth2_compute_engine_credentials.h index 9ead58012e4c9..add952892db64 100644 --- a/google/cloud/internal/oauth2_compute_engine_credentials.h +++ b/google/cloud/internal/oauth2_compute_engine_credentials.h @@ -118,6 +118,8 @@ class ComputeEngineCredentials : public Credentials { StatusOr project_id( google::cloud::Options const& options) const override; + AllowedLocationsRequestType AllowedLocationsRequest() const override; + /** * Returns the email or alias of this credential's service account. * diff --git a/google/cloud/internal/oauth2_compute_engine_credentials_test.cc b/google/cloud/internal/oauth2_compute_engine_credentials_test.cc index 7edb731392d14..d9c7042972280 100644 --- a/google/cloud/internal/oauth2_compute_engine_credentials_test.cc +++ b/google/cloud/internal/oauth2_compute_engine_credentials_test.cc @@ -53,6 +53,7 @@ using ::testing::Property; using ::testing::Return; using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAreArray; +using ::testing::VariantWith; using MockHttpClientFactory = ::testing::MockFunction( @@ -387,6 +388,10 @@ TEST(ComputeEngineCredentialsTest, FailedRefresh) { HasSubstr("Could not find all required fields"))); } +MATCHER_P(RequestServiceAccountEmailIs, email, "has service account email") { + return email == arg.service_account_email; +} + /// @test Verify that we can force a refresh of the service account email. TEST(ComputeEngineCredentialsTest, AccountEmail) { auto const alias = std::string{"default"}; @@ -416,6 +421,15 @@ TEST(ComputeEngineCredentialsTest, AccountEmail) { auto refreshed_email = credentials.AccountEmail(); EXPECT_EQ(email, refreshed_email); EXPECT_EQ(credentials.service_account_email(), refreshed_email); + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + EXPECT_THAT(credentials.AllowedLocationsRequest(), + VariantWith( + RequestServiceAccountEmailIs(email))); +#else + EXPECT_THAT(credentials.AllowedLocationsRequest(), + VariantWith(std::monostate())); +#endif } auto expected_universe_domain_request = []() { diff --git a/google/cloud/internal/oauth2_credentials.cc b/google/cloud/internal/oauth2_credentials.cc index 23301be9a1a44..4f85f961640f5 100644 --- a/google/cloud/internal/oauth2_credentials.cc +++ b/google/cloud/internal/oauth2_credentials.cc @@ -31,8 +31,9 @@ Credentials::AuthenticationHeaders(std::chrono::system_clock::time_point tp, if (!authorization->empty()) headers.push_back(*std::move(authorization)); auto allowed_locations = AllowedLocations(tp, endpoint); - // Not all credential types support the x-allowed-locations header. For those - // that do, if there is a problem retrieving the header, omit the header. + // Not all credential types support the x-allowed-locations header. For + // those that do, if there is a problem retrieving the header, omit the + // header. if (allowed_locations.ok() && !allowed_locations->empty()) { headers.push_back(*std::move(allowed_locations)); } diff --git a/google/cloud/internal/oauth2_credentials.h b/google/cloud/internal/oauth2_credentials.h index b52f285579543..7b8ece8c00ed0 100644 --- a/google/cloud/internal/oauth2_credentials.h +++ b/google/cloud/internal/oauth2_credentials.h @@ -22,6 +22,7 @@ #include "google/cloud/version.h" #include #include +#include #include namespace google { @@ -29,6 +30,19 @@ namespace cloud { namespace oauth2_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +struct ServiceAccountAllowedLocationsRequest { + std::string service_account_email; +}; + +struct WorkloadIdentityAllowedLocationsRequest { + std::string project_id; + std::string pool_id; +}; + +struct WorkforceIdentityAllowedLocationsRequest { + std::string pool_id; +}; + /** * Interface for OAuth 2.0 credentials for use with Google's Unified Auth Client * (GUAC) library. Internally, GUAC credentials are mapped to the appropriate @@ -69,9 +83,8 @@ class Credentials { * @param endpoint the endpoint of the GCP service the RPC request will be * sent to. */ - virtual StatusOr> - AuthenticationHeaders(std::chrono::system_clock::time_point tp, - std::string_view endpoint); + StatusOr> AuthenticationHeaders( + std::chrono::system_clock::time_point tp, std::string_view endpoint); /** * Try to sign @p string_to_sign using @p service_account. @@ -160,6 +173,20 @@ class Credentials { */ virtual StatusOr GetToken( std::chrono::system_clock::time_point tp) = 0; + + using AllowedLocationsRequestType = + std::variant; + /** + * Obtains the request type from the underlying credential, if supported. + * + * Not all credential types support the `x-allowed-locations` header, but + * those that do vary in the data needed to format the request to IAM. + */ + virtual AllowedLocationsRequestType AllowedLocationsRequest() const { + return std::monostate{}; + } }; GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/internal/oauth2_decorate_credentials.cc b/google/cloud/internal/oauth2_decorate_credentials.cc index d499a0737e04e..4df3206aec658 100644 --- a/google/cloud/internal/oauth2_decorate_credentials.cc +++ b/google/cloud/internal/oauth2_decorate_credentials.cc @@ -16,6 +16,7 @@ #include "google/cloud/common_options.h" #include "google/cloud/internal/oauth2_cached_credentials.h" #include "google/cloud/internal/oauth2_logging_credentials.h" +#include "google/cloud/internal/oauth2_regional_access_boundary_token_manager.h" namespace google { namespace cloud { @@ -23,10 +24,18 @@ namespace oauth2_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN std::shared_ptr Decorate( - std::shared_ptr impl, Options const& opts) { + std::shared_ptr impl, + HttpClientFactory client_factory, Options const& opts) { impl = WithLogging(std::move(impl), opts, "refresh"); impl = WithCaching(std::move(impl)); - return WithLogging(std::move(impl), opts, "cached"); + impl = WithLogging(std::move(impl), opts, "cached"); + if (!std::holds_alternative( + impl->AllowedLocationsRequest())) { + impl = WithRegionalAccessBoundary(std::move(impl), + std::move(client_factory), opts); + impl = WithLogging(std::move(impl), opts, "rab"); + } + return impl; } std::shared_ptr WithLogging( @@ -42,6 +51,13 @@ std::shared_ptr WithCaching( return std::make_shared(std::move(impl)); } +std::shared_ptr WithRegionalAccessBoundary( + std::shared_ptr impl, + HttpClientFactory client_factory, Options options) { + return RegionalAccessBoundaryTokenManager::Create( + std::move(impl), std::move(client_factory), std::move(options)); +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace oauth2_internal } // namespace cloud diff --git a/google/cloud/internal/oauth2_decorate_credentials.h b/google/cloud/internal/oauth2_decorate_credentials.h index 8863898f0044b..a0c27da4ecd98 100644 --- a/google/cloud/internal/oauth2_decorate_credentials.h +++ b/google/cloud/internal/oauth2_decorate_credentials.h @@ -16,6 +16,7 @@ #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_DECORATE_CREDENTIALS_H #include "google/cloud/internal/oauth2_credentials.h" +#include "google/cloud/internal/oauth2_http_client_factory.h" #include "google/cloud/options.h" #include "google/cloud/version.h" #include @@ -29,7 +30,8 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN /// Add a full stack of logging (if requested in @p opts) and caching decorators /// to the credentials. std::shared_ptr Decorate( - std::shared_ptr impl, Options const& opts); + std::shared_ptr impl, + HttpClientFactory client_factory, Options const& opts); /// Add only a logging decorator to the credentials if requested in @p opts std::shared_ptr WithLogging( @@ -40,6 +42,11 @@ std::shared_ptr WithLogging( std::shared_ptr WithCaching( std::shared_ptr impl); +/// Add regional access boundary decorator to the credentials. +std::shared_ptr WithRegionalAccessBoundary( + std::shared_ptr impl, + HttpClientFactory client_factory, Options options); + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace oauth2_internal } // namespace cloud diff --git a/google/cloud/internal/oauth2_external_account_credentials.cc b/google/cloud/internal/oauth2_external_account_credentials.cc index a9ab92ff1459a..c162b1d398719 100644 --- a/google/cloud/internal/oauth2_external_account_credentials.cc +++ b/google/cloud/internal/oauth2_external_account_credentials.cc @@ -25,6 +25,7 @@ #include "google/cloud/internal/rest_client.h" #include "absl/strings/str_cat.h" #include +#include namespace google { namespace cloud { @@ -49,8 +50,41 @@ StatusOr MakeExternalAccountTokenSource( GCP_ERROR_INFO().WithContext(ec)); } +std::variant +IdentityFederationFromAudience(std::string const& audience) { + auto constexpr kWorkloadPattern = + R"""(iam.googleapis.com/projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)/)"""; + static auto* workload_re = + new std::regex{kWorkloadPattern, std::regex::optimize}; + + auto constexpr kWorkforcePattern = + R"""(iam.googleapis.com/locations/global/workforcePools/([^/]+)/)"""; + static auto* workforce_re = + new std::regex{kWorkforcePattern, std::regex::optimize}; + + std::smatch match; + if (std::regex_search(audience, match, *workload_re)) { + return WorkloadIdentityFederationInfo{match[1], match[2]}; + } + if (std::regex_search(audience, match, *workforce_re)) { + return WorkforceIdentityFederationInfo{match[1]}; + } + return std::monostate{}; +} + } // namespace +bool ExternalAccountInfo::IsWorkforceIdentityFederation() const { + return std::holds_alternative( + identity_federation_info); +} + +bool ExternalAccountInfo::IsWorkloadIdentityFederation() const { + return std::holds_alternative( + identity_federation_info); +} + /// Parse a JSON string with an external account configuration. StatusOr ParseExternalAccountConfiguration( std::string const& configuration, internal::ErrorContext const& ec) { @@ -70,6 +104,8 @@ StatusOr ParseExternalAccountConfiguration( auto audience = ValidateStringField(json, "audience", "credentials-file", ec); if (!audience) return std::move(audience).status(); + auto identity_federation = IdentityFederationFromAudience(*audience); + auto subject_token_type = ValidateStringField(json, "subject_token_type", "credentials-file", ec); if (!subject_token_type) return std::move(subject_token_type).status(); @@ -108,7 +144,8 @@ StatusOr ParseExternalAccountConfiguration( *std::move(source), absl::nullopt, *std::move(universe_domain), - std::move(workforce_pool_user_project)}; + std::move(workforce_pool_user_project), + std::move(identity_federation)}; it = json.find("service_account_impersonation_url"); if (it == json.end()) return info; @@ -161,7 +198,8 @@ StatusOr ExternalAccountCredentials::GetToken( // Workforce Identity is handled at the org level and requires the userProject // header. Workload Identity is handled at the project level and doesn't // require the header. - if (info_.workforce_pool_user_project) { + if (info_.IsWorkforceIdentityFederation() && + info_.workforce_pool_user_project.has_value()) { form_data.emplace_back( "options", absl::StrCat(R"({"userProject": ")", *info_.workforce_pool_user_project, R"("})")); @@ -221,6 +259,25 @@ StatusOr ExternalAccountCredentials::GetToken( return AccessToken{*token, tp + std::chrono::seconds(*expires_in)}; } +Credentials::AllowedLocationsRequestType +ExternalAccountCredentials::AllowedLocationsRequest() const { + Credentials::AllowedLocationsRequestType request = std::monostate{}; + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + if (info_.IsWorkforceIdentityFederation()) { + auto wif = std::get( + info_.identity_federation_info); + request = WorkforceIdentityAllowedLocationsRequest{wif.pool_id}; + } else if (info_.IsWorkloadIdentityFederation()) { + auto wif = std::get( + info_.identity_federation_info); + request = + WorkloadIdentityAllowedLocationsRequest{wif.project_id, wif.pool_id}; + } +#endif + return request; +} + StatusOr ExternalAccountCredentials::GetTokenImpersonation( std::string const& access_token, internal::ErrorContext const& ec) { auto request = rest_internal::RestRequest(info_.impersonation_config->url); diff --git a/google/cloud/internal/oauth2_external_account_credentials.h b/google/cloud/internal/oauth2_external_account_credentials.h index efd9c425f800c..c4f46d30a30d5 100644 --- a/google/cloud/internal/oauth2_external_account_credentials.h +++ b/google/cloud/internal/oauth2_external_account_credentials.h @@ -55,6 +55,15 @@ struct ExternalAccountImpersonationConfig { std::chrono::seconds token_lifetime; }; +struct WorkforceIdentityFederationInfo { + std::string pool_id; +}; + +struct WorkloadIdentityFederationInfo { + std::string project_id; + std::string pool_id; +}; + /** * An external account configuration. * @@ -69,6 +78,11 @@ struct ExternalAccountInfo { absl::optional impersonation_config; std::string universe_domain; absl::optional workforce_pool_user_project; + std::variant + identity_federation_info; + bool IsWorkforceIdentityFederation() const; + bool IsWorkloadIdentityFederation() const; }; /// Parse a JSON string with an external account configuration. @@ -89,6 +103,8 @@ class ExternalAccountCredentials : public oauth2_internal::Credentials { return info_.universe_domain; } + AllowedLocationsRequestType AllowedLocationsRequest() const override; + private: StatusOr GetTokenImpersonation(std::string const& access_token, internal::ErrorContext const& ec); diff --git a/google/cloud/internal/oauth2_external_account_credentials_test.cc b/google/cloud/internal/oauth2_external_account_credentials_test.cc index d28a262df96b4..4bb47dc7701c2 100644 --- a/google/cloud/internal/oauth2_external_account_credentials_test.cc +++ b/google/cloud/internal/oauth2_external_account_credentials_test.cc @@ -54,6 +54,7 @@ using ::testing::Property; using ::testing::ResultOf; using ::testing::Return; using ::testing::UnorderedElementsAre; +using ::testing::VariantWith; using MockClientFactory = ::testing::MockFunction( @@ -135,6 +136,15 @@ struct TestOnlyOption { using Type = std::string; }; +MATCHER_P(WorkforceIdentityIs, pool_id, "has pool_id") { + return pool_id == arg.pool_id; +} + +MATCHER_P2(WorkloadIdentityIs, project_id, pool_id, + "has project_id and pool_id") { + return project_id == arg.project_id && pool_id == arg.pool_id; +} + TEST(ExternalAccount, ParseAwsSuccess) { // To simplify the test we provide all the parameters via environment // variables and avoid using imdsv2. @@ -180,6 +190,9 @@ TEST(ExternalAccount, ParseAwsSuccess) { EXPECT_EQ(actual->subject_token_type, kTestTokenType); EXPECT_EQ(actual->token_url, "test-token-url"); EXPECT_EQ(actual->universe_domain, kUniverseDomain); + EXPECT_THAT(actual->identity_federation_info, + VariantWith( + WorkloadIdentityIs("$PROJECT_NUMBER", "$POOL_ID"))); MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).Times(0); @@ -305,10 +318,13 @@ TEST(ExternalAccount, ParseWithImpersonationDefaultLifetimeSuccess) { std::chrono::seconds(3600)); } -TEST(ExternalAccount, ParseUserProjectSuccess) { +TEST(ExternalAccount, ParseWorkforceIdentityFederationSuccess) { + auto constexpr kWorkforceAudience = + "//iam.googleapis.com/locations/global/workforcePools/$POOL_ID/providers/" + "PROVIDER_ID"; auto const configuration = nlohmann::json{ {"type", "external_account"}, - {"audience", "test-audience"}, + {"audience", kWorkforceAudience}, {"subject_token_type", "test-subject-token-type"}, {"token_url", "test-token-url"}, {"credential_source", nlohmann::json{{"file", "/dev/null-test-only"}}}, @@ -319,11 +335,14 @@ TEST(ExternalAccount, ParseUserProjectSuccess) { auto const actual = ParseExternalAccountConfiguration(configuration.dump(), ec); ASSERT_STATUS_OK(actual); - EXPECT_EQ(actual->audience, "test-audience"); + EXPECT_EQ(actual->audience, kWorkforceAudience); EXPECT_EQ(actual->subject_token_type, "test-subject-token-type"); EXPECT_EQ(actual->token_url, "test-token-url"); EXPECT_THAT(actual->workforce_pool_user_project, Optional(std::string("project-id-or-name"))); + EXPECT_THAT(actual->identity_federation_info, + VariantWith( + WorkforceIdentityIs("$POOL_ID"))); } TEST(ExternalAccount, ParseNotJson) { @@ -676,11 +695,20 @@ TEST(ExternalAccount, Working) { auto mock_source = [](HttpClientFactory const&, Options const&) { return make_status_or(internal::SubjectToken{"test-subject-token"}); }; - auto const info = - ExternalAccountInfo{"test-audience", "test-subject-token-type", - test_url, mock_source, - absl::nullopt, {}, - absl::nullopt}; + + auto constexpr kTestAudience = + "//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/" + "workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID"; + + auto const info = ExternalAccountInfo{ + kTestAudience, + "test-subject-token-type", + test_url, + mock_source, + absl::nullopt, + {}, + absl::nullopt, + WorkloadIdentityFederationInfo{"$PROJECT_NUMBER", "$POOL_ID"}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call(make_expected_options())).WillOnce([&]() { @@ -693,7 +721,7 @@ TEST(ExternalAccount, Working) { Pair("requested_token_type", "urn:ietf:params:oauth:token-type:access_token"), Pair("scope", "https://www.googleapis.com/auth/cloud-platform"), - Pair("audience", "test-audience"), + Pair("audience", kTestAudience), Pair("subject_token_type", "test-subject-token-type"), Pair("subject_token", "test-subject-token"))); EXPECT_CALL(*mock, Post(_, expected_request, expected_payload)) @@ -710,6 +738,15 @@ TEST(ExternalAccount, Working) { ASSERT_STATUS_OK(access_token); EXPECT_EQ(access_token->expiration, now + expected_expires_in); EXPECT_EQ(access_token->token, expected_access_token); + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + EXPECT_THAT(credentials.AllowedLocationsRequest(), + VariantWith( + WorkloadIdentityIs("$PROJECT_NUMBER", "$POOL_ID"))); +#else + EXPECT_THAT(credentials.AllowedLocationsRequest(), + VariantWith(std::monostate{})); +#endif } TEST(ExternalAccount, WorkingWorkforceIdentity) { @@ -725,13 +762,15 @@ TEST(ExternalAccount, WorkingWorkforceIdentity) { auto mock_source = [](HttpClientFactory const&, Options const&) { return make_status_or(internal::SubjectToken{"test-subject-token"}); }; - auto const info = ExternalAccountInfo{"test-audience", - "test-subject-token-type", - test_url, - mock_source, - absl::nullopt, - {}, - "project-id-or-name"}; + auto const info = + ExternalAccountInfo{"test-audience", + "test-subject-token-type", + test_url, + mock_source, + absl::nullopt, + {}, + "project-id-or-name", + WorkforceIdentityFederationInfo{"$POOL_ID"}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call(make_expected_options())).WillOnce([&]() { @@ -762,6 +801,15 @@ TEST(ExternalAccount, WorkingWorkforceIdentity) { ASSERT_STATUS_OK(access_token); EXPECT_EQ(access_token->expiration, now + expected_expires_in); EXPECT_EQ(access_token->token, expected_access_token); + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + EXPECT_THAT(credentials.AllowedLocationsRequest(), + VariantWith( + WorkforceIdentityIs("$POOL_ID"))); +#else + EXPECT_THAT(credentials.AllowedLocationsRequest(), + VariantWith(std::monostate{})); +#endif } TEST(ExternalAccount, WorkingWithImpersonation) { @@ -803,7 +851,8 @@ TEST(ExternalAccount, WorkingWithImpersonation) { ExternalAccountImpersonationConfig{ impersonate_test_url, impersonate_test_lifetime}, {}, - absl::nullopt}; + absl::nullopt, + std::monostate{}}; auto sts_client = [&] { auto expected_sts_request = Property(&RestRequest::path, sts_test_url); @@ -875,7 +924,7 @@ TEST(ExternalAccount, HandleHttpError) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -914,7 +963,7 @@ TEST(ExternalAccount, HandleHttpPartialError) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -954,7 +1003,7 @@ TEST(ExternalAccount, HandleNotJson) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -994,7 +1043,7 @@ TEST(ExternalAccount, HandleNotJsonObject) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -1040,7 +1089,7 @@ TEST(ExternalAccount, MissingToken) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -1075,7 +1124,7 @@ TEST(ExternalAccount, MissingIssuedTokenType) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -1110,7 +1159,7 @@ TEST(ExternalAccount, MissingTokenType) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -1145,7 +1194,7 @@ TEST(ExternalAccount, InvalidIssuedTokenType) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -1182,7 +1231,7 @@ TEST(ExternalAccount, InvalidTokenType) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -1220,7 +1269,7 @@ TEST(ExternalAccount, MissingExpiresIn) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); @@ -1256,7 +1305,7 @@ TEST(ExternalAccount, InvalidExpiresIn) { ExternalAccountInfo{"test-audience", "test-subject-token-type", test_url, mock_source, absl::nullopt, {}, - absl::nullopt}; + absl::nullopt, std::monostate{}}; MockClientFactory client_factory; EXPECT_CALL(client_factory, Call).WillOnce([&]() { auto mock = std::make_unique(); diff --git a/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc b/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc index a2da884ed20bc..ee1bc933f00ac 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc @@ -146,11 +146,23 @@ ImpersonateServiceAccountCredentials::ImpersonateServiceAccountCredentials( ImpersonateServiceAccountCredentials::ImpersonateServiceAccountCredentials( google::cloud::internal::ImpersonateServiceAccountConfig const& config, std::shared_ptr stub) - : stub_(std::move(stub)), request_(MakeRequest(config)) {} + : stub_(std::move(stub)), + access_token_request_(MakeRequest(config)), + allowed_locations_request_({config.target_service_account()}) {} StatusOr ImpersonateServiceAccountCredentials::GetToken( std::chrono::system_clock::time_point /*tp*/) { - return stub_->GenerateAccessToken(request_); + return stub_->GenerateAccessToken(access_token_request_); +} + +Credentials::AllowedLocationsRequestType +ImpersonateServiceAccountCredentials::AllowedLocationsRequest() const { + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + return allowed_locations_request_; +#else + return std::monostate{}; +#endif } GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/internal/oauth2_impersonate_service_account_credentials.h b/google/cloud/internal/oauth2_impersonate_service_account_credentials.h index cdd9d82607fb6..c5d656ef7c589 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials.h +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials.h @@ -66,9 +66,12 @@ class ImpersonateServiceAccountCredentials return stub_->universe_domain(options); } + AllowedLocationsRequestType AllowedLocationsRequest() const override; + private: std::shared_ptr stub_; - GenerateAccessTokenRequest request_; + GenerateAccessTokenRequest access_token_request_; + ServiceAccountAllowedLocationsRequest allowed_locations_request_; }; GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc b/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc index 19da1679f3257..9a54e4c7c4ae7 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc @@ -36,6 +36,7 @@ using ::testing::ElementsAre; using ::testing::HasSubstr; using ::testing::Optional; using ::testing::Return; +using ::testing::VariantWith; auto constexpr kFullValidConfig = R"""({ "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/sa3@developer.gserviceaccount.com:generateAccessToken", @@ -165,6 +166,10 @@ class MockMinimalIamCredentialsRest : public MinimalIamCredentialsRest { (override, const)); }; +MATCHER_P(RequestServiceAccountEmailIs, email, "has service account email") { + return email == arg.service_account_email; +} + TEST(ImpersonateServiceAccountCredentialsTest, Basic) { auto const now = std::chrono::system_clock::now(); @@ -193,6 +198,16 @@ TEST(ImpersonateServiceAccountCredentialsTest, Basic) { token = under_test.GetToken(now + minutes(45)); ASSERT_THAT(token, StatusIs(StatusCode::kPermissionDenied)); + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + EXPECT_THAT( + under_test.AllowedLocationsRequest(), + VariantWith( + RequestServiceAccountEmailIs("test-only-invalid@test.invalid"))); +#else + EXPECT_THAT(under_test.AllowedLocationsRequest(), + VariantWith(std::monostate())); +#endif } TEST(ParseImpersonatedServiceAccountCredentialsWithoutAction, Success) { diff --git a/google/cloud/internal/oauth2_logging_credentials.cc b/google/cloud/internal/oauth2_logging_credentials.cc index f88b1efc89c73..8d62adb6a0fc8 100644 --- a/google/cloud/internal/oauth2_logging_credentials.cc +++ b/google/cloud/internal/oauth2_logging_credentials.cc @@ -96,6 +96,12 @@ StatusOr LoggingCredentials::project_id( return impl_->project_id(options); } +Credentials::AllowedLocationsRequestType +LoggingCredentials::AllowedLocationsRequest() const { + GCP_LOG(DEBUG) << __func__ << "(" << phase_ << ")"; + return impl_->AllowedLocationsRequest(); +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace oauth2_internal } // namespace cloud diff --git a/google/cloud/internal/oauth2_logging_credentials.h b/google/cloud/internal/oauth2_logging_credentials.h index 259f8baf03ffa..076a6bb6b881f 100644 --- a/google/cloud/internal/oauth2_logging_credentials.h +++ b/google/cloud/internal/oauth2_logging_credentials.h @@ -58,6 +58,8 @@ class LoggingCredentials : public Credentials { StatusOr universe_domain(Options const& options) const override; StatusOr project_id() const override; StatusOr project_id(Options const& options) const override; + Credentials::AllowedLocationsRequestType AllowedLocationsRequest() + const override; private: std::string phase_; diff --git a/google/cloud/internal/oauth2_minimal_iam_credentials_rest.h b/google/cloud/internal/oauth2_minimal_iam_credentials_rest.h index fda4f1f9bb4e6..95593624b618c 100644 --- a/google/cloud/internal/oauth2_minimal_iam_credentials_rest.h +++ b/google/cloud/internal/oauth2_minimal_iam_credentials_rest.h @@ -39,19 +39,6 @@ struct GenerateAccessTokenRequest { std::vector delegates; }; -struct ServiceAccountAllowedLocationsRequest { - std::string service_account_email; -}; - -struct WorkloadIdentityAllowedLocationsRequest { - std::string project_id; - std::string pool_id; -}; - -struct WorkforceIdentityAllowedLocationsRequest { - std::string pool_id; -}; - struct AllowedLocationsResponse { std::vector locations; std::string encoded_locations; diff --git a/google/cloud/internal/oauth2_regional_access_boundary_token_manager.cc b/google/cloud/internal/oauth2_regional_access_boundary_token_manager.cc new file mode 100644 index 0000000000000..279fe542d6b11 --- /dev/null +++ b/google/cloud/internal/oauth2_regional_access_boundary_token_manager.cc @@ -0,0 +1,262 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/internal/oauth2_regional_access_boundary_token_manager.h" +#include "google/cloud/internal/algorithm.h" +#include "google/cloud/internal/rest_response.h" +#include "google/cloud/log.h" +#include "absl/strings/str_cat.h" + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { + +auto constexpr kTokenTtl = std::chrono::seconds(6 * 3600); +auto constexpr kTtlGracePeriod = std::chrono::seconds(3600); +auto constexpr kMaximumRetryDuration = std::chrono::seconds(60); +auto constexpr kInitialBackoffDelay = std::chrono::seconds(1); +auto constexpr kMaximumBackoffDelay = std::chrono::seconds(5); +auto constexpr kBackoffScaling = 2.0; +auto constexpr kFailedLookupInitialBackoffDelay = std::chrono::seconds(15); +auto constexpr kFailedLookupMaximumBackoffDelay = std::chrono::seconds(120); +auto constexpr kFailedLookupBackoffScaling = 1.75; + +} // namespace + +bool RegionalAccessBoundaryTokenManager::RetryTraits::IsPermanentFailure( + Status const& s) { + // Http status codes 500, 502, 503, and 504 are mapped to kUnavailable, and + // some others that we don't mind retrying. + return s.code() != StatusCode::kUnavailable; +} + +class RegionalAccessBoundaryTokenManager::RefreshTokenLimitedTimeRetryPolicy + : public RefreshTokenRetryPolicy { + public: + template + explicit RefreshTokenLimitedTimeRetryPolicy( + std::chrono::duration maximum_duration) + : impl_(maximum_duration) {} + + RefreshTokenLimitedTimeRetryPolicy( + RefreshTokenLimitedTimeRetryPolicy&& rhs) noexcept + : RefreshTokenLimitedTimeRetryPolicy(rhs.maximum_duration()) {} + RefreshTokenLimitedTimeRetryPolicy( + RefreshTokenLimitedTimeRetryPolicy const& rhs) noexcept + : RefreshTokenLimitedTimeRetryPolicy(rhs.maximum_duration()) {} + + std::chrono::milliseconds maximum_duration() const { + return impl_.maximum_duration(); + } + + bool OnFailure(Status const& status) override { + return impl_.OnFailure(status); + } + bool IsExhausted() const override { return impl_.IsExhausted(); } + bool IsPermanentFailure(Status const& status) const override { + return impl_.IsPermanentFailure(status); + } + std::unique_ptr clone() const override { + return std::make_unique( + maximum_duration()); + } + + // This is provided only for backwards compatibility. + using BaseType = RefreshTokenRetryPolicy; + + private: + google::cloud::internal::LimitedTimeRetryPolicy impl_; +}; + +std::shared_ptr +RegionalAccessBoundaryTokenManager::Create(std::shared_ptr child, + HttpClientFactory client_factory, + Options options) { + auto iam_stub = MakeMinimalIamCredentialsRestStub(child, options, + std::move(client_factory)); + return std::shared_ptr( + new RegionalAccessBoundaryTokenManager( + std::move(child), std::move(iam_stub), + std::make_unique< + rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(), + std::move(options), FailedLookupBackoffPolicy, + std::make_shared())); +} + +std::shared_ptr +RegionalAccessBoundaryTokenManager::Create( + std::shared_ptr child, + std::shared_ptr iam_stub, Options options) { + return std::shared_ptr( + new RegionalAccessBoundaryTokenManager( + std::move(child), std::move(iam_stub), + std::make_unique< + rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(), + std::move(options), FailedLookupBackoffPolicy, + std::make_shared())); +} + +std::shared_ptr +RegionalAccessBoundaryTokenManager::Create( + std::shared_ptr child, + std::shared_ptr iam_stub, Options options, + std::function()> + failed_lookup_backoff_policy_fn, + std::shared_ptr clock, AllowedLocationsResponse allowed_locations) { + return std::shared_ptr( + new RegionalAccessBoundaryTokenManager( + std::move(child), std::move(iam_stub), + std::make_unique< + rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(), + std::move(options), std::move(failed_lookup_backoff_policy_fn), + std::move(clock), std::move(allowed_locations))); +} + +std::unique_ptr +RegionalAccessBoundaryTokenManager::FailedLookupBackoffPolicy() { + return std::make_unique( + kFailedLookupInitialBackoffDelay, kFailedLookupMaximumBackoffDelay, + kFailedLookupBackoffScaling); +} + +RegionalAccessBoundaryTokenManager::RegionalAccessBoundaryTokenManager( + std::shared_ptr child, + std::shared_ptr iam_stub, + std::unique_ptr background, + Options options, + std::function()> + failed_lookup_backoff_policy_fn, + std::shared_ptr clock, AllowedLocationsResponse allowed_locations) + : child_(std::move(child)), + background_(std::move(background)), + options_(std::move(options)), + clock_(std::move(clock)), + retry_policy_(std::make_unique( + kMaximumRetryDuration)), + backoff_policy_(std::make_unique( + kInitialBackoffDelay, kMaximumBackoffDelay, kBackoffScaling)), + failed_lookup_backoff_policy_fn_( + std::move(failed_lookup_backoff_policy_fn)), + iam_stub_(std::move(iam_stub)), + allowed_locations_(std::move(allowed_locations)) { + if (!allowed_locations_.encoded_locations.empty()) { + expire_time_ = clock_->Now() + TokenTtl(); + } +} + +bool RegionalAccessBoundaryTokenManager::DoesEndpointRequireToken( + std::string_view endpoint) { + return absl::EndsWithIgnoreCase(endpoint, ".googleapis.com") && + !absl::EndsWithIgnoreCase(endpoint, ".rep.googleapis.com") && + !absl::EndsWithIgnoreCase(endpoint, (".rep.sandbox.googleapis.com")); +} + +bool RegionalAccessBoundaryTokenManager::IsTokenValid( + std::scoped_lock const&, + std::chrono::system_clock::time_point tp) const { + return !allowed_locations_.encoded_locations.empty() && tp < expire_time_; +} + +std::chrono::seconds RegionalAccessBoundaryTokenManager::TtlGracePeriod() { + return kTtlGracePeriod; +} + +std::chrono::seconds RegionalAccessBoundaryTokenManager::TokenTtl() { + return kTokenTtl; +} + +StatusOr +RegionalAccessBoundaryTokenManager::AllowedLocations( + std::chrono::system_clock::time_point tp, std::string_view endpoint) { + auto request = child_->AllowedLocationsRequest(); + struct Visitor { + StatusOr operator()(std::monostate) const { + return rest_internal::HttpHeader{}; + } + StatusOr operator()( + ServiceAccountAllowedLocationsRequest const& r) const { + return m.GetAllowedLocationsHeader(r, tp, endpoint); + } + StatusOr operator()( + WorkforceIdentityAllowedLocationsRequest const& r) const { + return m.GetAllowedLocationsHeader(r, tp, endpoint); + } + StatusOr operator()( + WorkloadIdentityAllowedLocationsRequest const& r) const { + return m.GetAllowedLocationsHeader(r, tp, endpoint); + } + + RegionalAccessBoundaryTokenManager& m; + std::chrono::system_clock::time_point tp; + std::string_view endpoint; + }; + return std::visit(Visitor{*this, tp, endpoint}, request); +} + +StatusOr> +RegionalAccessBoundaryTokenManager::SignBlob( + absl::optional const& signing_service_account, + std::string const& string_to_sign) const { + return child_->SignBlob(signing_service_account, string_to_sign); +} + +std::string RegionalAccessBoundaryTokenManager::AccountEmail() const { + return child_->AccountEmail(); +} + +std::string RegionalAccessBoundaryTokenManager::KeyId() const { + return child_->KeyId(); +} + +StatusOr RegionalAccessBoundaryTokenManager::universe_domain() + const { + return child_->universe_domain(); +} + +StatusOr RegionalAccessBoundaryTokenManager::universe_domain( + google::cloud::Options const& options) const { + return child_->universe_domain(options); +} + +StatusOr RegionalAccessBoundaryTokenManager::project_id() const { + return child_->project_id(); +} + +StatusOr RegionalAccessBoundaryTokenManager::project_id( + Options const& options) const { + return child_->project_id(options); +} + +StatusOr +RegionalAccessBoundaryTokenManager::Authorization( + std::chrono::system_clock::time_point tp) { + return child_->Authorization(tp); +} + +StatusOr RegionalAccessBoundaryTokenManager::GetToken( + std::chrono::system_clock::time_point tp) { + return child_->GetToken(tp); +} + +Credentials::AllowedLocationsRequestType +RegionalAccessBoundaryTokenManager::AllowedLocationsRequest() const { + return child_->AllowedLocationsRequest(); +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google diff --git a/google/cloud/internal/oauth2_regional_access_boundary_token_manager.h b/google/cloud/internal/oauth2_regional_access_boundary_token_manager.h new file mode 100644 index 0000000000000..85d1ba3f01245 --- /dev/null +++ b/google/cloud/internal/oauth2_regional_access_boundary_token_manager.h @@ -0,0 +1,252 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_REGIONAL_ACCESS_BOUNDARY_TOKEN_MANAGER_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_REGIONAL_ACCESS_BOUNDARY_TOKEN_MANAGER_H + +#include "google/cloud/backoff_policy.h" +#include "google/cloud/internal/clock.h" +#include "google/cloud/internal/http_header.h" +#include "google/cloud/internal/oauth2_minimal_iam_credentials_rest.h" +#include "google/cloud/internal/rest_pure_background_threads_impl.h" +#include "google/cloud/internal/rest_retry_loop.h" +#include "google/cloud/internal/retry_policy_impl.h" +#include "google/cloud/log.h" +#include "google/cloud/options.h" +#include "google/cloud/status_or.h" +#include "google/cloud/version.h" +#include "absl/strings/match.h" +#include +#include + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/** + * Manages fetching and caching routing tokens for RAB. + * + * Regional endpoints (those ending with ".rep.googleapis.com" or + * ".rep.sandbox.googleapis.com") do not require a routing token. Non-GDU + * endpoints, likewise do not require a routing token. + * + * The routing tokens are refreshed asynchronously by a background thread. + * + * Supported credential types: + * - Service Account + * - Impersonated Service Account + * - Workload Identity Federation + * - Workforce Identity Federation + * - Self Signed JWT + * + * Supported environments: + * - GDU only + */ +class RegionalAccessBoundaryTokenManager + : public Credentials, + public std::enable_shared_from_this { + public: + using Clock = ::google::cloud::internal::SystemClock; + + struct RetryTraits { + static bool IsPermanentFailure(Status const&); + }; + + static std::shared_ptr Create( + std::shared_ptr child, HttpClientFactory client_factory, + Options options); + + static std::shared_ptr Create( + std::shared_ptr child, + std::shared_ptr iam_stub, Options options); + + static std::chrono::seconds TtlGracePeriod(); + static std::chrono::seconds TokenTtl(); + + // Decorator overrides from Credentials that simply call the same method on + // child_. + StatusOr> SignBlob( + absl::optional const& signing_service_account, + std::string const& string_to_sign) const override; + std::string AccountEmail() const override; + std::string KeyId() const override; + StatusOr universe_domain() const override; + StatusOr universe_domain( + google::cloud::Options const& options) const override; + StatusOr project_id() const override; + StatusOr project_id(Options const&) const override; + StatusOr Authorization( + std::chrono::system_clock::time_point tp) override; + StatusOr GetToken( + std::chrono::system_clock::time_point tp) override; + Credentials::AllowedLocationsRequestType AllowedLocationsRequest() + const override; + + // Decorator override that has an implementation. + StatusOr AllowedLocations( + std::chrono::system_clock::time_point tp, + std::string_view endpoint) override; + + template + StatusOr GetAllowedLocationsHeader( + Request const& request, std::chrono::system_clock::time_point tp, + std::string_view endpoint) { + // If the endpoint does not need a token, return immediately. + if (!DoesEndpointRequireToken(endpoint)) return rest_internal::HttpHeader{}; + std::scoped_lock lock(mu_); + if (!refresh_in_progress_ && pending_refresh_.valid()) { + pending_refresh_.get(); + } + if (IsTokenValid(lock, tp)) { + // Check to see if we're near expiry and if so, start refresh process. + if (tp > (expire_time_ - TtlGracePeriod())) RefreshToken(lock, request); + return rest_internal::HttpHeader{"x-allowed-locations", + allowed_locations_.encoded_locations}; + } + RefreshToken(lock, request); + // Don't wait for a valid token, just return an empty header. + return rest_internal::HttpHeader{}; + } + + // Used for testing. + static std::shared_ptr Create( + std::shared_ptr child, + std::shared_ptr iam_stub, Options options, + std::function()> + failed_lookup_backoff_policy_fn, + std::shared_ptr clock = std::make_shared(), + AllowedLocationsResponse allowed_locations = AllowedLocationsResponse{}); + + // Snapshot read useful only in testing. + bool IsOnCooldown() const { + std::scoped_lock lock(mu_); + return failed_lookup_cooldown_.valid() && + !failed_lookup_cooldown_.is_ready(); + } + + // Snapshot read useful only in testing. + bool IsRefreshPending() const { + std::scoped_lock lock(mu_); + return refresh_in_progress_; + } + + private: + class RefreshTokenRetryPolicy : public ::google::cloud::RetryPolicy { + public: + virtual std::unique_ptr clone() const = 0; + }; + + class RefreshTokenLimitedTimeRetryPolicy; + + static bool DoesEndpointRequireToken(std::string_view endpoint); + static std::unique_ptr FailedLookupBackoffPolicy(); + + RegionalAccessBoundaryTokenManager( + std::shared_ptr child, + std::shared_ptr iam_stub, + std::unique_ptr background, + Options options, + std::function()> + failed_lookup_backoff_policy_fn, + std::shared_ptr clock = std::make_shared(), + AllowedLocationsResponse allowed_locations = AllowedLocationsResponse{}); + + bool IsTokenValid(std::scoped_lock const&, + std::chrono::system_clock::time_point tp) const; + + template + void RefreshToken(std::scoped_lock const&, + Request const& request) { + if (refresh_in_progress_) return; + if (failed_lookup_cooldown_.valid()) { + if (!failed_lookup_cooldown_.is_ready()) return; + (void)failed_lookup_cooldown_.get(); + } + + promise pending_refresh; + pending_refresh_ = pending_refresh.get_future(); + auto constexpr kLocation = __func__; + auto pending_refresh_fn = [p = std::move(pending_refresh), + weak = weak_from_this(), request, + stub = iam_stub_, + retry_policy = retry_policy_->clone(), + backoff_policy = backoff_policy_->clone(), + options = options_]() mutable { + auto refresh_attempt_fn = [stub](rest_internal::RestContext&, + Options const&, Request const& request) { + return stub->AllowedLocations(request); + }; + + StatusOr allowed_locations = + rest_internal::RestRetryLoop( + *retry_policy, *backoff_policy, Idempotency::kIdempotent, + refresh_attempt_fn, options, request, kLocation); + + if (!allowed_locations.ok()) { + GCP_LOG(WARNING) << "AllowedLocations refresh failed with status=" + << allowed_locations.status(); + } + + auto self = weak.lock(); + if (!self) return; + std::scoped_lock lock(self->mu_); + if (allowed_locations.ok()) { + self->allowed_locations_ = *allowed_locations; + self->expire_time_ = self->clock_->Now() + TokenTtl(); + self->failed_lookup_backoff_policy_.reset(); + p.set_value(Status{}); + } else { + self->allowed_locations_ = AllowedLocationsResponse{}; + if (!self->failed_lookup_backoff_policy_) { + self->failed_lookup_backoff_policy_ = + self->failed_lookup_backoff_policy_fn_(); + } + self->failed_lookup_cooldown_ = + self->background_->cq().MakeRelativeTimer( + self->failed_lookup_backoff_policy_->OnCompletion()); + p.set_value(allowed_locations.status()); + } + self->refresh_in_progress_ = false; + }; + + refresh_in_progress_ = true; + background_->cq().RunAsync(std::move(pending_refresh_fn)); + } + + mutable std::mutex mu_; + std::shared_ptr child_; + std::unique_ptr background_; + Options options_; + std::shared_ptr clock_; + std::unique_ptr retry_policy_; + std::unique_ptr backoff_policy_; + std::function()> + failed_lookup_backoff_policy_fn_; + std::unique_ptr failed_lookup_backoff_policy_; + std::shared_ptr iam_stub_; + future> + failed_lookup_cooldown_; + std::chrono::system_clock::time_point expire_time_; + AllowedLocationsResponse allowed_locations_; + future pending_refresh_; + bool refresh_in_progress_ = false; +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_REGIONAL_ACCESS_BOUNDARY_TOKEN_MANAGER_H diff --git a/google/cloud/internal/oauth2_regional_access_boundary_token_manager_test.cc b/google/cloud/internal/oauth2_regional_access_boundary_token_manager_test.cc new file mode 100644 index 0000000000000..7a3e43e49b851 --- /dev/null +++ b/google/cloud/internal/oauth2_regional_access_boundary_token_manager_test.cc @@ -0,0 +1,380 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/internal/oauth2_regional_access_boundary_token_manager.h" +#include "google/cloud/internal/make_status.h" +#include "google/cloud/internal/rest_pure_background_threads_impl.h" +#include "google/cloud/internal/rest_response.h" +#include "google/cloud/internal/unified_rest_credentials.h" +#include "google/cloud/testing_util/fake_clock.h" +#include "google/cloud/testing_util/status_matchers.h" +#include + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { + +using ::google::cloud::testing_util::IsOkAndHolds; +using ::testing::A; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::MockFunction; +using ::testing::Return; + +class MockCredentials : public Credentials { + public: + MOCK_METHOD(StatusOr>, SignBlob, + (absl::optional const&, std::string const&), + (const, override)); + MOCK_METHOD(std::string, AccountEmail, (), (const, override)); + MOCK_METHOD(std::string, KeyId, (), (const, override)); + MOCK_METHOD(StatusOr, universe_domain, (), (const, override)); + MOCK_METHOD(StatusOr, universe_domain, + (google::cloud::Options const&), (const, override)); + MOCK_METHOD(StatusOr, project_id, (), (const, override)); + MOCK_METHOD(StatusOr, project_id, (Options const&), + (const, override)); + MOCK_METHOD(StatusOr, Authorization, + (std::chrono::system_clock::time_point), (override)); + MOCK_METHOD(StatusOr, AllowedLocations, + (std::chrono::system_clock::time_point, std::string_view), + (override)); + MOCK_METHOD(StatusOr, GetToken, + (std::chrono::system_clock::time_point), (override)); + MOCK_METHOD(AllowedLocationsRequestType, AllowedLocationsRequest, (), + (const, override)); +}; + +class MockMinimalIamCredentialsRest : public MinimalIamCredentialsRest { + public: + MOCK_METHOD(StatusOr, GenerateAccessToken, + (GenerateAccessTokenRequest const&), (override)); + MOCK_METHOD(StatusOr, AllowedLocations, + (ServiceAccountAllowedLocationsRequest const&), (override)); + MOCK_METHOD(StatusOr, AllowedLocations, + (WorkloadIdentityAllowedLocationsRequest const&), (override)); + MOCK_METHOD(StatusOr, AllowedLocations, + (WorkforceIdentityAllowedLocationsRequest const&), (override)); + MOCK_METHOD(StatusOr, universe_domain, (Options const& options), + (override, const)); +}; + +class MockBackoffPolicy : public BackoffPolicy { + public: + MOCK_METHOD(std::unique_ptr, clone, (), (const, override)); + MOCK_METHOD(std::chrono::milliseconds, OnCompletion, (), (override)); +}; + +TEST(RegionalAccessBoundaryTokenManagerRetryTraitsTest, RetryTraits) { + auto http_status_500 = + rest_internal::AsStatus(rest_internal::kInternalServerError, {}); + auto http_status_502 = + rest_internal::AsStatus(rest_internal::kBadGateway, {}); + auto http_status_503 = + rest_internal::AsStatus(rest_internal::kServiceUnavailable, {}); + auto http_status_504 = + rest_internal::AsStatus(rest_internal::kGatewayTimeout, {}); + + using RetryTraits = RegionalAccessBoundaryTokenManager::RetryTraits; + + EXPECT_FALSE(RetryTraits::IsPermanentFailure(http_status_500)); + EXPECT_FALSE(RetryTraits::IsPermanentFailure(http_status_502)); + EXPECT_FALSE(RetryTraits::IsPermanentFailure(http_status_503)); + EXPECT_FALSE(RetryTraits::IsPermanentFailure(http_status_504)); + + auto http_status_404 = rest_internal::AsStatus(rest_internal::kNotFound, {}); + EXPECT_TRUE(RetryTraits::IsPermanentFailure(http_status_404)); +} + +class RegionalAccessBoundaryTokenManagerTest : public ::testing::Test { + protected: + RegionalAccessBoundaryTokenManagerTest() + : mock_credentials_(std::make_shared()), + mock_iam_stub_(std::make_shared()), + fake_clock_(std::make_shared()) {} + + std::shared_ptr mock_credentials_; + std::shared_ptr mock_iam_stub_; + std::shared_ptr fake_clock_; +}; + +TEST_F(RegionalAccessBoundaryTokenManagerTest, + GetAllowedLocationsHeaderNonApplicableEndpoints) { + auto manager = RegionalAccessBoundaryTokenManager::Create(mock_credentials_, + mock_iam_stub_, {}); + + ServiceAccountAllowedLocationsRequest request; + auto header = manager->GetAllowedLocationsHeader( + request, std::chrono::system_clock::now(), "service.rep.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); + + auto header_sandbox = manager->GetAllowedLocationsHeader( + request, std::chrono::system_clock::now(), + "service.rep.sandbox.googleapis.com"); + EXPECT_THAT(header_sandbox, IsOkAndHolds(IsEmpty())); + + auto header_non_gdu = manager->GetAllowedLocationsHeader( + request, std::chrono::system_clock::now(), "service.bar.com"); + EXPECT_THAT(header_non_gdu, IsOkAndHolds(IsEmpty())); +} + +TEST_F(RegionalAccessBoundaryTokenManagerTest, + GetAllowedLocationsHeaderValidTokenSoftExpiry) { + fake_clock_->SetTime(std::chrono::system_clock::now()); + MockFunction()> backoff_fn; + EXPECT_CALL(backoff_fn, Call).Times(0); + + EXPECT_CALL(*mock_credentials_, AllowedLocationsRequest) + .WillRepeatedly(Return(WorkforceIdentityAllowedLocationsRequest{})); + + AllowedLocationsResponse allowed_locations; + allowed_locations.locations = {"location1"}; + allowed_locations.encoded_locations = "encoded-location"; + + auto manager = RegionalAccessBoundaryTokenManager::Create( + mock_credentials_, mock_iam_stub_, {}, backoff_fn.AsStdFunction(), + fake_clock_, allowed_locations); + + fake_clock_->AdvanceTime(std::chrono::seconds(1)); + + auto header = + manager->AllowedLocations(fake_clock_->Now(), "service.googleapis.com"); + EXPECT_THAT(header, + IsOkAndHolds(rest_internal::HttpHeader{ + "x-allowed-locations", allowed_locations.encoded_locations})); + + promise sync_threads; + AllowedLocationsResponse refreshed_allowed_locations; + refreshed_allowed_locations.locations = {"location2"}; + refreshed_allowed_locations.encoded_locations = "encoded-location-2"; + // Refresh is called due to soft expiry, but current token is still returned. + EXPECT_CALL( + *mock_iam_stub_, + AllowedLocations(A())) + .WillOnce([&, f = sync_threads.get_future()]( + WorkforceIdentityAllowedLocationsRequest const&) mutable { + f.get(); + return refreshed_allowed_locations; + }); + + fake_clock_->AdvanceTime( + RegionalAccessBoundaryTokenManager::TokenTtl() - + RegionalAccessBoundaryTokenManager::TtlGracePeriod()); + + header = + manager->AllowedLocations(fake_clock_->Now(), "service.googleapis.com"); + EXPECT_THAT(header, + IsOkAndHolds(rest_internal::HttpHeader{ + "x-allowed-locations", allowed_locations.encoded_locations})); + EXPECT_TRUE(manager->IsRefreshPending()); + sync_threads.set_value(); + + // Give background thread a chance to call AllowedLocations. + std::this_thread::sleep_for(std::chrono::seconds(2)); + header = + manager->AllowedLocations(fake_clock_->Now(), "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(rest_internal::HttpHeader{ + "x-allowed-locations", + refreshed_allowed_locations.encoded_locations})); +} + +TEST_F(RegionalAccessBoundaryTokenManagerTest, + GetAllowedLocationsHeaderNoInitialValidTokenWithRetry) { + EXPECT_CALL(*mock_credentials_, AllowedLocationsRequest) + .WillRepeatedly(Return(WorkloadIdentityAllowedLocationsRequest{})); + + AllowedLocationsResponse response; + response.locations = {"location1"}; + response.encoded_locations = "encoded-location"; + EXPECT_CALL( + *mock_iam_stub_, + AllowedLocations(A())) + .WillOnce([&](WorkloadIdentityAllowedLocationsRequest const&) { + return internal::UnavailableError("unavailable"); + }) + .WillOnce([&](WorkloadIdentityAllowedLocationsRequest const&) { + return response; + }); + + auto manager = RegionalAccessBoundaryTokenManager::Create(mock_credentials_, + mock_iam_stub_, {}); + + auto header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); + + // Give the background thread a chance to run the future::then callback + // and update the token. + std::this_thread::sleep_for(std::chrono::seconds(2)); + + header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(rest_internal::HttpHeader{ + "x-allowed-locations", response.encoded_locations})); +} + +TEST_F(RegionalAccessBoundaryTokenManagerTest, + GetAllowedLocationsHeaderPermanentFailureAndRecovery) { + EXPECT_CALL(*mock_credentials_, AllowedLocationsRequest) + .WillRepeatedly(Return(ServiceAccountAllowedLocationsRequest{})); + + AllowedLocationsResponse response; + response.locations = {"location1"}; + response.encoded_locations = "encoded-location"; + EXPECT_CALL( + *mock_iam_stub_, + AllowedLocations(A())) + .WillOnce([&](ServiceAccountAllowedLocationsRequest const&) { + return internal::UnavailableError("unavailable"); + }) + .WillOnce([&](ServiceAccountAllowedLocationsRequest const&) { + return internal::InternalError("uh oh"); + }) + .WillOnce([&](ServiceAccountAllowedLocationsRequest const&) { + return response; + }); + + MockFunction()> backoff_fn; + EXPECT_CALL(backoff_fn, Call).WillOnce([]() { + auto mock_backoff = std::make_unique(); + EXPECT_CALL(*mock_backoff, OnCompletion) + .WillOnce(Return(std::chrono::milliseconds(1000))); + return mock_backoff; + }); + + auto manager = RegionalAccessBoundaryTokenManager::Create( + mock_credentials_, mock_iam_stub_, {}, backoff_fn.AsStdFunction()); + + auto header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); + + // Give the background thread a chance to run and update the token. + std::this_thread::sleep_for(std::chrono::seconds(2)); + + header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); + // Permanent error encountered; verify cooldown has been set. + EXPECT_TRUE(manager->IsOnCooldown()); + + // With no valid token and active cooldown, no call to AllowedLocations on the + // iam stub should occur. + header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); + + // With the mock backoff returning a short failure cooldown, let it expire. + std::this_thread::sleep_for(std::chrono::seconds(1)); + + header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); + + // Give the background thread a chance to run and update the token. + std::this_thread::sleep_for(std::chrono::seconds(2)); + + header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(rest_internal::HttpHeader{ + "x-allowed-locations", response.encoded_locations})); +} + +TEST_F(RegionalAccessBoundaryTokenManagerTest, + GetAllowedLocationsMonostateRequest) { + EXPECT_CALL(*mock_credentials_, AllowedLocationsRequest) + .WillRepeatedly(Return(std::monostate{})); + + EXPECT_CALL( + *mock_iam_stub_, + AllowedLocations(A())) + .Times(0); + EXPECT_CALL( + *mock_iam_stub_, + AllowedLocations(A())) + .Times(0); + EXPECT_CALL( + *mock_iam_stub_, + AllowedLocations(A())) + .Times(0); + + auto manager = RegionalAccessBoundaryTokenManager::Create(mock_credentials_, + mock_iam_stub_, {}); + auto header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); + + // Give the background thread a chance to run the future::then callback + // and update the token. + std::this_thread::sleep_for(std::chrono::seconds(2)); + + header = manager->AllowedLocations(std::chrono::system_clock::now(), + "service.googleapis.com"); + EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); +} + +TEST_F(RegionalAccessBoundaryTokenManagerTest, DecoratorMethodPassThrough) { + auto now = std::chrono::system_clock::now(); + auto options = Options{}.set("my-user-project"); + + EXPECT_CALL(*mock_credentials_, SignBlob(Eq("sa"), Eq("string"))) + .WillOnce(Return(StatusOr>({42}))); + EXPECT_CALL(*mock_credentials_, AccountEmail).WillOnce(Return("my-email")); + EXPECT_CALL(*mock_credentials_, KeyId).WillOnce(Return("my-keyid")); + EXPECT_CALL(*mock_credentials_, universe_domain()) + .WillOnce(Return(StatusOr("my-ud"))); + EXPECT_CALL(*mock_credentials_, + universe_domain(A())) + .WillOnce([](Options const& opts) -> StatusOr { + EXPECT_EQ(opts.get(), "my-user-project"); + return std::string{"my-ud"}; + }); + EXPECT_CALL(*mock_credentials_, project_id()) + .WillOnce(Return(StatusOr("my-project"))); + EXPECT_CALL(*mock_credentials_, + project_id(A())) + .WillOnce([](Options const& opts) -> StatusOr { + EXPECT_EQ(opts.get(), "my-user-project"); + return std::string{"my-project"}; + }); + EXPECT_CALL(*mock_credentials_, Authorization(Eq(now))) + .WillOnce(Return(StatusOr{})); + EXPECT_CALL(*mock_credentials_, GetToken(Eq(now))) + .WillOnce(Return(StatusOr{})); + EXPECT_CALL(*mock_credentials_, AllowedLocationsRequest) + .WillOnce( + Return(Credentials::AllowedLocationsRequestType{std::monostate{}})); + + auto manager = RegionalAccessBoundaryTokenManager::Create(mock_credentials_, + mock_iam_stub_, {}); + + (void)manager->SignBlob("sa", "string"); + (void)manager->AccountEmail(); + (void)manager->KeyId(); + (void)manager->universe_domain(); + (void)manager->universe_domain(options); + (void)manager->project_id(); + (void)manager->project_id(options); + (void)manager->Authorization(now); + (void)manager->GetToken(now); + (void)manager->AllowedLocationsRequest(); +} + +} // namespace +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google diff --git a/google/cloud/internal/oauth2_service_account_credentials.cc b/google/cloud/internal/oauth2_service_account_credentials.cc index 849f572211bfe..9bfb7281eaf69 100644 --- a/google/cloud/internal/oauth2_service_account_credentials.cc +++ b/google/cloud/internal/oauth2_service_account_credentials.cc @@ -370,6 +370,16 @@ StatusOr ServiceAccountCredentials::project_id( return project_id(); } +Credentials::AllowedLocationsRequestType +ServiceAccountCredentials::AllowedLocationsRequest() const { + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + return ServiceAccountAllowedLocationsRequest{info_.client_email}; +#else + return std::monostate{}; +#endif +} + bool ServiceAccountUseOAuth(ServiceAccountCredentialsInfo const& info) { // Custom universe domains are only supported with JWT, not OAuth tokens. if (info.universe_domain.has_value() && diff --git a/google/cloud/internal/oauth2_service_account_credentials.h b/google/cloud/internal/oauth2_service_account_credentials.h index 6a62388c18ec9..748af3e9903d5 100644 --- a/google/cloud/internal/oauth2_service_account_credentials.h +++ b/google/cloud/internal/oauth2_service_account_credentials.h @@ -293,6 +293,8 @@ class ServiceAccountCredentials : public oauth2_internal::Credentials { StatusOr project_id() const override; StatusOr project_id(Options const&) const override; + AllowedLocationsRequestType AllowedLocationsRequest() const override; + private: bool UseOAuth(); StatusOr GetTokenOAuth( diff --git a/google/cloud/internal/oauth2_service_account_credentials_test.cc b/google/cloud/internal/oauth2_service_account_credentials_test.cc index a8bd0b6e23e0d..07531618f20f5 100644 --- a/google/cloud/internal/oauth2_service_account_credentials_test.cc +++ b/google/cloud/internal/oauth2_service_account_credentials_test.cc @@ -56,6 +56,7 @@ using ::testing::Not; using ::testing::Pair; using ::testing::Property; using ::testing::Return; +using ::testing::VariantWith; using MockHttpClientFactory = ::testing::MockFunction( @@ -138,6 +139,10 @@ std::string MakeUniverseDomainTestContents() { return json.dump(); } +MATCHER_P(RequestServiceAccountEmailIs, email, "has service account email") { + return email == arg.service_account_email; +} + void CheckInfoYieldsExpectedAssertion(ServiceAccountCredentialsInfo const& info, std::string const& assertion, std::time_t assertion_time) { @@ -181,6 +186,15 @@ void CheckInfoYieldsExpectedAssertion(ServiceAccountCredentialsInfo const& info, ASSERT_STATUS_OK(token); EXPECT_EQ(token->token, "access-token-value"); EXPECT_EQ(token->expiration, tp + std::chrono::seconds(1234)); + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + EXPECT_THAT(credentials.AllowedLocationsRequest(), + VariantWith( + RequestServiceAccountEmailIs(kClientEmail))); +#else + EXPECT_THAT(credentials.AllowedLocationsRequest(), + VariantWith(std::monostate())); +#endif } TEST(ServiceAccountCredentialsTest, ServiceAccountUseOAuth) { diff --git a/google/cloud/internal/rest_completion_queue_impl.cc b/google/cloud/internal/rest_completion_queue_impl.cc index b9c6e9c6ff32a..0791c471a1daf 100644 --- a/google/cloud/internal/rest_completion_queue_impl.cc +++ b/google/cloud/internal/rest_completion_queue_impl.cc @@ -13,7 +13,6 @@ // limitations under the License. #include "google/cloud/internal/rest_completion_queue_impl.h" -#include "google/cloud/internal/timer_queue.h" namespace google { namespace cloud { @@ -21,42 +20,28 @@ namespace rest_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN RestCompletionQueueImpl::RestCompletionQueueImpl() - : tq_(internal::TimerQueue::Create()) {} + : impl_(std::make_shared()) {} -void RestCompletionQueueImpl::Run() { tq_->Service(); } +void RestCompletionQueueImpl::Run() { impl_->Run(); } -void RestCompletionQueueImpl::Shutdown() { tq_->Shutdown(); } +void RestCompletionQueueImpl::Shutdown() { impl_->Shutdown(); } void RestCompletionQueueImpl::CancelAll() {} future> RestCompletionQueueImpl::MakeDeadlineTimer( std::chrono::system_clock::time_point deadline) { - return tq_->Schedule(deadline); + return impl_->MakeDeadlineTimer(deadline); } future> RestCompletionQueueImpl::MakeRelativeTimer(std::chrono::nanoseconds duration) { - using std::chrono::system_clock; - auto d = std::chrono::duration_cast(duration); - if (d < duration) d += system_clock::duration{1}; - return MakeDeadlineTimer(system_clock::now() + d); + return impl_->MakeRelativeTimer(duration); } -// Use an "immediately" expiring timer in order to get the thread(s) servicing -// the TimerQueue to execute the function. However, if the timer -// expires before .then() is invoked, the lambda will be immediately called and -// the passing of execution to the queue servicing thread will not occur. void RestCompletionQueueImpl::RunAsync( std::unique_ptr function) { - ++run_async_counter_; - tq_->Schedule([f = std::move(function)](auto) { f->exec(); }); -} - -void RestCompletionQueueImpl::StartOperation( - std::shared_ptr, - absl::FunctionRef) { - GCP_LOG(FATAL) << " function not supported.\n"; + impl_->RunAsync(std::move(function)); } GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/internal/rest_completion_queue_impl.h b/google/cloud/internal/rest_completion_queue_impl.h index b90df4cde65eb..44ccea0528fe2 100644 --- a/google/cloud/internal/rest_completion_queue_impl.h +++ b/google/cloud/internal/rest_completion_queue_impl.h @@ -17,6 +17,7 @@ #include "google/cloud/future.h" #include "google/cloud/internal/completion_queue_impl.h" +#include "google/cloud/internal/rest_pure_completion_queue_impl.h" #include "google/cloud/internal/timer_queue.h" #include "google/cloud/log.h" #include "google/cloud/status_or.h" @@ -69,19 +70,18 @@ class RestCompletionQueueImpl final /// This function is not supported by RestCompletionQueueImpl, but as the /// function is pure virtual, it must be overridden. void StartOperation(std::shared_ptr, - absl::FunctionRef) override; + absl::FunctionRef) override { + GCP_LOG(FATAL) << " function not supported.\n"; + } /// The underlying gRPC completion queue, which does not exist. grpc::CompletionQueue* cq() override { return nullptr; } /// Some counters for testing and debugging. - std::int64_t run_async_counter() const { return run_async_counter_.load(); } + std::int64_t run_async_counter() const { return impl_->run_async_counter(); } private: - std::shared_ptr tq_; - - // These are metrics used in testing. - std::atomic run_async_counter_{0}; + std::shared_ptr impl_; }; GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/internal/rest_pure_background_threads_impl.cc b/google/cloud/internal/rest_pure_background_threads_impl.cc new file mode 100644 index 0000000000000..525b92c0adb79 --- /dev/null +++ b/google/cloud/internal/rest_pure_background_threads_impl.cc @@ -0,0 +1,73 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/internal/rest_pure_background_threads_impl.h" +#include "google/cloud/future.h" +#include "google/cloud/internal/call_context.h" +#include "google/cloud/internal/rest_pure_completion_queue_impl.h" +#include "google/cloud/log.h" +#include +#include + +namespace google { +namespace cloud { +namespace rest_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +AutomaticallyCreatedRestPureBackgroundThreads:: + AutomaticallyCreatedRestPureBackgroundThreads(std::size_t thread_count) + : cq_(std::make_shared()), + pool_(thread_count == 0 ? 1 : thread_count) { + std::generate_n(pool_.begin(), pool_.size(), [this] { + promise started; + auto thread = std::thread( + [](RestPureCompletionQueue cq, promise& started, + internal::CallContext c) { + internal::ScopedCallContext scope(std::move(c)); + started.set_value(); + cq.Run(); + }, + cq_, std::ref(started), internal::CallContext{}); + started.get_future().wait(); + return thread; + }); +} + +AutomaticallyCreatedRestPureBackgroundThreads:: + ~AutomaticallyCreatedRestPureBackgroundThreads() { + Shutdown(); +} + +void AutomaticallyCreatedRestPureBackgroundThreads::Shutdown() { + cq_.Shutdown(); + for (auto& t : pool_) { +#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS + try { +#endif + t.join(); +#if GOOGLE_CLOUD_CPP_HAVE_EXCEPTIONS + } catch (std::system_error const& e) { + GCP_LOG(FATAL) + << "AutomaticallyCreatedRestPureBackgroundThreads::Shutdown: " + << e.what(); + } +#endif + } + pool_.clear(); +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace rest_internal +} // namespace cloud +} // namespace google diff --git a/google/cloud/internal/rest_pure_background_threads_impl.h b/google/cloud/internal/rest_pure_background_threads_impl.h new file mode 100644 index 0000000000000..4061729dd7f63 --- /dev/null +++ b/google/cloud/internal/rest_pure_background_threads_impl.h @@ -0,0 +1,62 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_REST_PURE_BACKGROUND_THREADS_IMPL_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_REST_PURE_BACKGROUND_THREADS_IMPL_H + +#include "google/cloud/internal/rest_pure_completion_queue_impl.h" +#include "google/cloud/version.h" +#include +#include + +namespace google { +namespace cloud { +namespace rest_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/// This interface mimics `google::cloud::BackgroundThreads` except it returns +/// a `RestPureCompletionQueue` instead of a `CompletionQueue`. This is +/// important as `CompletionQueue` has gRPC dependencies we need to avoid in a +/// pure REST library. +class RestPureBackgroundThreads { + public: + virtual ~RestPureBackgroundThreads() = default; + + /// The completion queue used for the background operations. + virtual RestPureCompletionQueue cq() const = 0; +}; + +/// Background threads that run on a RestPureCompletionQueue. +class AutomaticallyCreatedRestPureBackgroundThreads + : public RestPureBackgroundThreads { + public: + explicit AutomaticallyCreatedRestPureBackgroundThreads( + std::size_t thread_count = 1U); + ~AutomaticallyCreatedRestPureBackgroundThreads() override; + + RestPureCompletionQueue cq() const override { return cq_; } + void Shutdown(); + std::size_t pool_size() const { return pool_.size(); } + + private: + RestPureCompletionQueue cq_; + std::vector pool_; +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace rest_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_REST_PURE_BACKGROUND_THREADS_IMPL_H diff --git a/google/cloud/internal/rest_pure_completion_queue_impl.cc b/google/cloud/internal/rest_pure_completion_queue_impl.cc new file mode 100644 index 0000000000000..5eb70581d1358 --- /dev/null +++ b/google/cloud/internal/rest_pure_completion_queue_impl.cc @@ -0,0 +1,63 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/internal/rest_pure_completion_queue_impl.h" +#include "google/cloud/internal/timer_queue.h" + +namespace google { +namespace cloud { +namespace rest_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +RestPureCompletionQueue::RestPureCompletionQueue() + : impl_(std::make_shared()) {} + +RestPureCompletionQueueImpl::RestPureCompletionQueueImpl() + : tq_(internal::TimerQueue::Create()) {} + +void RestPureCompletionQueueImpl::Run() { tq_->Service(); } + +void RestPureCompletionQueueImpl::Shutdown() { tq_->Shutdown(); } + +void RestPureCompletionQueueImpl::CancelAll() {} + +future> +RestPureCompletionQueueImpl::MakeDeadlineTimer( + std::chrono::system_clock::time_point deadline) { + return tq_->Schedule(deadline); +} + +future> +RestPureCompletionQueueImpl::MakeRelativeTimer( + std::chrono::nanoseconds duration) { + using std::chrono::system_clock; + auto d = std::chrono::duration_cast(duration); + if (d < duration) d += system_clock::duration{1}; + return MakeDeadlineTimer(system_clock::now() + d); +} + +// Use an "immediately" expiring timer in order to get the thread(s) servicing +// the TimerQueue to execute the function. However, if the timer +// expires before .then() is invoked, the lambda will be immediately called and +// the passing of execution to the queue servicing thread will not occur. +void RestPureCompletionQueueImpl::RunAsync( + std::unique_ptr function) { + ++run_async_counter_; + tq_->Schedule([f = std::move(function)](auto) { f->exec(); }); +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace rest_internal +} // namespace cloud +} // namespace google diff --git a/google/cloud/internal/rest_pure_completion_queue_impl.h b/google/cloud/internal/rest_pure_completion_queue_impl.h new file mode 100644 index 0000000000000..d1e8d77bcb365 --- /dev/null +++ b/google/cloud/internal/rest_pure_completion_queue_impl.h @@ -0,0 +1,266 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_REST_PURE_COMPLETION_QUEUE_IMPL_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_REST_PURE_COMPLETION_QUEUE_IMPL_H + +#include "google/cloud/future.h" +#include "google/cloud/internal/run_async_base.h" +#include "google/cloud/internal/timer_queue.h" +#include "google/cloud/log.h" +#include "google/cloud/status_or.h" +#include "google/cloud/version.h" +#include +#include +#include + +namespace google { +namespace cloud { +namespace rest_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/** + * Interface for CompletionQueue that does NOT use a grpc::CompletionQueue. + * + * Due to the lack of a completion queue that can manage multiple, simultaneous + * REST requests, asynchronous calls should be launched on a thread of their own + * and RunAsync should only be called with a function to join that thread after + * it completes its work. + * + * This class differs from RestCompletionQueueImpl as it does not contain a + * method that returns a `grpc::CompletionQueue*` nor a `StartOperation` method + * as that method takes an `internal::AsyncGrpcOperation` as a parameter. + */ +class RestPureCompletionQueueInterface { + public: + virtual ~RestPureCompletionQueueInterface() = default; + + /// Run the event loop until Shutdown() is called. + virtual void Run() = 0; + + /// Terminate the event loop. + virtual void Shutdown() = 0; + + /// Cancel all existing operations. + virtual void CancelAll() = 0; + + /// Create a new timer. + virtual future> + MakeDeadlineTimer(std::chrono::system_clock::time_point deadline) = 0; + + /// Create a new timer. + virtual future> + MakeRelativeTimer(std::chrono::nanoseconds duration) = 0; + + /// Enqueue a new asynchronous function. + virtual void RunAsync(std::unique_ptr function) = 0; +}; + +class RestPureCompletionQueue; + +std::shared_ptr +GetRestPureCompletionQueueImpl(RestPureCompletionQueue const& cq); +std::shared_ptr +GetRestPureCompletionQueueImpl(RestPureCompletionQueue&& cq); + +template +using CheckRunAsyncCallback = + google::cloud::internal::is_invocable; + +/// A pure REST version of `google::cloud::CompletionQueue` that wraps an +/// implementation of `RestPureCompletionQueueInterface`. +class RestPureCompletionQueue { + public: + RestPureCompletionQueue(); + explicit RestPureCompletionQueue( + std::shared_ptr impl) + : impl_(std::move(impl)) {} + + /** + * Run the completion queue event loop. + * + * Note that more than one thread can call this member function, to create a + * pool of threads completing asynchronous operations. + */ + void Run() { impl_->Run(); } + + /// Terminate the completion queue event loop. + void Shutdown() { impl_->Shutdown(); } + + /// Cancel all pending operations. + void CancelAll() { impl_->CancelAll(); } + + /** + * Create a timer that fires at @p deadline. + * + * @param deadline when should the timer expire. + * + * @return a future that becomes satisfied after @p deadline. + * The result of the future is the time at which it expired, or an error + * Status if the timer did not run to expiration (e.g. it was cancelled). + */ + google::cloud::future> + MakeDeadlineTimer(std::chrono::system_clock::time_point deadline) { + return impl_->MakeDeadlineTimer(deadline); + } + + /** + * Create a timer that fires after the @p duration. + * + * @tparam Rep a placeholder to match the Rep tparam for @p duration type, + * the semantics of this template parameter are documented in + * `std::chrono::duration<>` (in brief, the underlying arithmetic type + * used to store the number of ticks), for our purposes it is simply a + * formal parameter. + * @tparam Period a placeholder to match the Period tparam for @p duration + * type, the semantics of this template parameter are documented in + * `std::chrono::duration<>` (in brief, the length of the tick in seconds, + * expressed as a `std::ratio<>`), for our purposes it is simply a formal + * parameter. + * + * @param duration when should the timer expire relative to the current time. + * + * @return a future that becomes satisfied after @p duration time has elapsed. + * The result of the future is the time at which it expired, or an error + * Status if the timer did not run to expiration (e.g. it was cancelled). + */ + template + future> MakeRelativeTimer( + std::chrono::duration duration) { + return impl_->MakeRelativeTimer( + std::chrono::duration_cast(duration)); + } + + /** + * Asynchronously run a functor on a thread `Run()`ning the `CompletionQueue`. + * + * @param functor the functor to invoke in one of the CompletionQueue's + * threads. + * + * @tparam Functor the type of @p functor. It must satisfy + * `std::is_invocable` + */ + template ::value, int> = 0 + /// @endcond + > + void RunAsync(Functor&& functor) { + class Wrapper : public internal::RunAsyncBase { + public: + Wrapper(std::weak_ptr impl, Functor&& f) + : impl_(std::move(impl)), fun_(std::forward(f)) {} + ~Wrapper() override = default; + void exec() override { + auto impl = impl_.lock(); + if (!impl) return; + RestPureCompletionQueue cq(std::move(impl)); + fun_(cq); + } + + private: + std::weak_ptr impl_; + std::decay_t fun_; + }; + impl_->RunAsync( + std::make_unique(impl_, std::forward(functor))); + } + + /** + * Asynchronously run a functor on a thread `Run()`ning the `CompletionQueue`. + * + * @param functor the functor to call in one of the CompletionQueue's threads. + * @tparam Functor the type of @p functor. It must satisfy + * `std::is_invocable`. + */ + template ::value, int> = 0 + /// @endcond + > + void RunAsync(Functor&& functor) { + class Wrapper : public internal::RunAsyncBase { + public: + explicit Wrapper(Functor&& f) : fun_(std::forward(f)) {} + ~Wrapper() override = default; + void exec() override { fun_(); } + + private: + std::decay_t fun_; + }; + impl_->RunAsync(std::make_unique(std::forward(functor))); + } + + private: + friend std::shared_ptr + GetRestPureCompletionQueueImpl(RestPureCompletionQueue const& cq); + friend std::shared_ptr + GetRestPureCompletionQueueImpl(RestPureCompletionQueue&& cq); + std::shared_ptr impl_; +}; + +inline std::shared_ptr +GetRestPureCompletionQueueImpl(RestPureCompletionQueue const& cq) { + return cq.impl_; +} + +inline std::shared_ptr +GetRestPureCompletionQueueImpl(RestPureCompletionQueue&& cq) { + return std::move(cq.impl_); +} + +/// Default implementation of `RestPureCompletionQueueInterface` that uses a +/// `TimerQueue` under the hood. +class RestPureCompletionQueueImpl final + : public RestPureCompletionQueueInterface, + public std::enable_shared_from_this { + public: + ~RestPureCompletionQueueImpl() override = default; + RestPureCompletionQueueImpl(); + + /// Run the event loop until Shutdown() is called. + void Run() override; + + /// Terminate the event loop. + void Shutdown() override; + + /// Cancel all existing operations. + void CancelAll() override; + + /// Create a new timer. + future> MakeDeadlineTimer( + std::chrono::system_clock::time_point deadline) override; + + /// Create a new timer. + future> MakeRelativeTimer( + std::chrono::nanoseconds duration) override; + + /// Enqueue a new asynchronous function. + void RunAsync(std::unique_ptr function) override; + + /// Some counters for testing and debugging. + std::int64_t run_async_counter() const { return run_async_counter_.load(); } + + private: + std::shared_ptr tq_; + // These are metrics used in testing. + std::atomic run_async_counter_{0}; +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace rest_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_REST_PURE_COMPLETION_QUEUE_IMPL_H diff --git a/google/cloud/internal/rest_response.cc b/google/cloud/internal/rest_response.cc index 53383bfe2b779..7eb750e3e40d6 100644 --- a/google/cloud/internal/rest_response.cc +++ b/google/cloud/internal/rest_response.cc @@ -120,6 +120,9 @@ StatusCode MapHttpCodeToStatus5xx(std::int32_t code) { if (code == HttpStatusCode::kServiceUnavailable) { return StatusCode::kUnavailable; } + if (code == HttpStatusCode::kGatewayTimeout) { + return StatusCode::kUnavailable; + } // 5XX - server errors are mapped to kInternal. return StatusCode::kInternal; } @@ -153,8 +156,12 @@ Status AsStatus(HttpStatusCode http_status_code, std::string payload) { if (payload.empty()) { // If there's no payload, create one to make sure the original http status // code received is available. - return Status(status_code, "Received HTTP status code: " + - std::to_string(http_status_code)); + ErrorInfo error_info{ + {}, {}, {{"http_status_code", std::to_string(http_status_code)}}}; + return Status( + status_code, + "Received HTTP status code: " + std::to_string(http_status_code), + std::move(error_info)); } auto p = ParseJsonError(static_cast(http_status_code), std::move(payload)); diff --git a/google/cloud/internal/rest_response.h b/google/cloud/internal/rest_response.h index 2a5aae8a50db4..0cb7bb0ea6f50 100644 --- a/google/cloud/internal/rest_response.h +++ b/google/cloud/internal/rest_response.h @@ -67,6 +67,7 @@ enum HttpStatusCode : std::int32_t { kInternalServerError = 500, kBadGateway = 502, kServiceUnavailable = 503, + kGatewayTimeout = 504, }; // This class contains the results of making a request to a RESTful service. diff --git a/google/cloud/internal/rest_response_test.cc b/google/cloud/internal/rest_response_test.cc index 1d2245095b915..7a9f0878fbeda 100644 --- a/google/cloud/internal/rest_response_test.cc +++ b/google/cloud/internal/rest_response_test.cc @@ -83,7 +83,8 @@ INSTANTIATE_TEST_SUITE_P( std::make_pair(HttpStatusCode::kBadGateway, StatusCode::kUnavailable), std::make_pair(HttpStatusCode::kServiceUnavailable, StatusCode::kUnavailable), - std::make_pair(static_cast(504), StatusCode::kInternal), + std::make_pair(HttpStatusCode::kGatewayTimeout, + StatusCode::kUnavailable), std::make_pair(static_cast(601), StatusCode::kUnknown)), [](testing::TestParamInfo const& info) { return std::to_string(std::get<0>(info.param)); @@ -135,6 +136,8 @@ TEST(AsStatus, RestResponseIsNotOkNoPayload) { EXPECT_THAT(status, StatusIs(StatusCode::kNotFound)); EXPECT_THAT(status.message(), Eq("Received HTTP status code: 404")); EXPECT_TRUE(status.error_info().reason().empty()); + EXPECT_THAT(status.error_info().metadata(), + Contains(::testing::Pair("http_status_code", "404"))); } TEST(AsStatus, RestResponseIsNotOkPayload) { diff --git a/google/cloud/internal/run_async_base.h b/google/cloud/internal/run_async_base.h new file mode 100644 index 0000000000000..e01e99be6f65d --- /dev/null +++ b/google/cloud/internal/run_async_base.h @@ -0,0 +1,36 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_RUN_ASYNC_BASE_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_RUN_ASYNC_BASE_H + +#include "google/cloud/version.h" + +namespace google { +namespace cloud { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace internal { + +// Type erase the callables in RunAsync() +struct RunAsyncBase { + virtual ~RunAsyncBase() = default; + virtual void exec() = 0; +}; + +} // namespace internal +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_RUN_ASYNC_BASE_H diff --git a/google/cloud/internal/unified_rest_credentials.cc b/google/cloud/internal/unified_rest_credentials.cc index 34c9ef726c53d..596617c57d7d9 100644 --- a/google/cloud/internal/unified_rest_credentials.cc +++ b/google/cloud/internal/unified_rest_credentials.cc @@ -81,9 +81,10 @@ std::shared_ptr MapCredentials( void visit(GoogleDefaultCredentialsConfig const& cfg) override { auto credentials = google::cloud::oauth2_internal::GoogleDefaultCredentials( - cfg.options(), std::move(client_factory_)); + cfg.options(), client_factory_); if (credentials) { - result = Decorate(*std::move(credentials), cfg.options()); + result = Decorate(*std::move(credentials), std::move(client_factory_), + cfg.options()); return; } result = MakeErrorCredentials(std::move(credentials).status()); @@ -97,19 +98,20 @@ std::shared_ptr MapCredentials( void visit(ImpersonateServiceAccountConfig const& cfg) override { result = std::make_shared< oauth2_internal::ImpersonateServiceAccountCredentials>( - cfg, std::move(client_factory_)); - result = Decorate(std::move(result), cfg.options()); + cfg, client_factory_); + result = Decorate(std::move(result), std::move(client_factory_), + cfg.options()); } void visit(ServiceAccountConfig const& cfg) override { StatusOr> creds; if (cfg.file_path().has_value()) { creds = oauth2_internal::CreateServiceAccountCredentialsFromFilePath( - *cfg.file_path(), cfg.options(), std::move(client_factory_)); + *cfg.file_path(), cfg.options(), client_factory_); } else if (cfg.json_object().has_value()) { creds = oauth2_internal::CreateServiceAccountCredentialsFromJsonContents( - *cfg.json_object(), cfg.options(), std::move(client_factory_)); + *cfg.json_object(), cfg.options(), client_factory_); } else { creds = MakeErrorCredentials(internal::InternalError( "ServiceAccountConfig has neither json_object nor file_path", @@ -117,7 +119,7 @@ std::shared_ptr MapCredentials( } if (creds.ok()) { - result = Decorate(*creds, cfg.options()); + result = Decorate(*creds, std::move(client_factory_), cfg.options()); } else { result = MakeErrorCredentials(std::move(creds).status()); } @@ -131,10 +133,11 @@ std::shared_ptr MapCredentials( result = MakeErrorCredentials(std::move(info).status()); return; } - result = Decorate( + auto creds = std::make_shared( - *std::move(info), std::move(client_factory_), cfg.options()), - cfg.options()); + *std::move(info), client_factory_, cfg.options()); + result = + Decorate(std::move(creds), std::move(client_factory_), cfg.options()); } void visit(ApiKeyConfig const& cfg) override { @@ -143,11 +146,11 @@ std::shared_ptr MapCredentials( } void visit(ComputeEngineCredentialsConfig const& cfg) override { - result = Decorate( - std::make_shared< - google::cloud::oauth2_internal::ComputeEngineCredentials>( - cfg.options(), std::move(client_factory_)), - cfg.options()); + auto creds = std::make_shared< + google::cloud::oauth2_internal::ComputeEngineCredentials>( + cfg.options(), client_factory_); + result = + Decorate(std::move(creds), std::move(client_factory_), cfg.options()); } private: diff --git a/google/cloud/internal/unified_rest_credentials_test.cc b/google/cloud/internal/unified_rest_credentials_test.cc index b3d769bf462f0..ab66fa764858d 100644 --- a/google/cloud/internal/unified_rest_credentials_test.cc +++ b/google/cloud/internal/unified_rest_credentials_test.cc @@ -244,7 +244,7 @@ TEST(UnifiedRestCredentialsTest, AdcIsComputeEngine) { ScopedEnvironment(oauth2_internal::GoogleGcloudAdcFileEnvVar(), filename); auto const now = std::chrono::system_clock::now(); - auto metadata_client = []() { + auto metadata_client_1 = []() { auto client = std::make_unique(); auto expected_request = AllOf( Property(&RestRequest::path, @@ -258,6 +258,27 @@ TEST(UnifiedRestCredentialsTest, AdcIsComputeEngine) { Status{StatusCode::kPermissionDenied, "uh-oh - GCE metadata"})); return client; }(); + + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + // If the first MDS call is not successful in updating the service account + // email, we will try again. + auto metadata_client_2 = []() { + auto client = std::make_unique(); + auto expected_request = AllOf( + Property(&RestRequest::path, + absl::StrCat("http://metadata.google.internal/", + "computeMetadata/v1/instance/service-accounts/", + "default/")), + Property(&RestRequest::headers, + Contains(Pair("metadata-flavor", Contains("Google"))))); + EXPECT_CALL(*client, Get(_, expected_request)) + .WillOnce(Return( + Status{StatusCode::kPermissionDenied, "uh-oh - GCE metadata"})); + return client; + }(); +#endif + auto token_client = []() { auto client = std::make_unique(); auto expected_request = AllOf( @@ -275,7 +296,11 @@ TEST(UnifiedRestCredentialsTest, AdcIsComputeEngine) { MockClientFactory client_factory; EXPECT_CALL(client_factory, Call) - .WillOnce(Return(ByMove(std::move(metadata_client)))) + .WillOnce(Return(ByMove(std::move(metadata_client_1)))) + // TODO(#16079): Remove conditional and else clause when GA. +#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB + .WillOnce(Return(ByMove(std::move(metadata_client_2)))) +#endif .WillOnce(Return(ByMove(std::move(token_client)))); auto const config = internal::GoogleDefaultCredentialsConfig(Options{}); diff --git a/google/cloud/internal/version_info.h b/google/cloud/internal/version_info.h index dc6892bcc13fa..27173b2d238b8 100644 --- a/google/cloud/internal/version_info.h +++ b/google/cloud/internal/version_info.h @@ -18,9 +18,9 @@ // NOLINTNEXTLINE(modernize-macro-to-enum) #define GOOGLE_CLOUD_CPP_VERSION_MAJOR 3 // NOLINTNEXTLINE(modernize-macro-to-enum) -#define GOOGLE_CLOUD_CPP_VERSION_MINOR 4 +#define GOOGLE_CLOUD_CPP_VERSION_MINOR 5 // NOLINTNEXTLINE(modernize-macro-to-enum) #define GOOGLE_CLOUD_CPP_VERSION_PATCH 0 -#define GOOGLE_CLOUD_CPP_VERSION_PRE_RELEASE "" +#define GOOGLE_CLOUD_CPP_VERSION_PRE_RELEASE "rc" #endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_VERSION_INFO_H diff --git a/google/cloud/storage/internal/async/connection_impl.cc b/google/cloud/storage/internal/async/connection_impl.cc index e67993317b6d0..409a45fbf1f35 100644 --- a/google/cloud/storage/internal/async/connection_impl.cc +++ b/google/cloud/storage/internal/async/connection_impl.cc @@ -334,9 +334,11 @@ AsyncConnectionImpl::AppendableObjectUploadImpl(AppendableUploadParams p) { auto factory = WriteResultFactory( [stub = stub_, cq = cq_, retry = std::move(retry), // NOLINTNEXTLINE(bugprone-lambda-function-name) - backoff = std::move(backoff), current, function_name = __func__]( + backoff = std::move(backoff), current, function_name = __func__, + // Use shared_ptr to propagate RoutingHeaderOptions across retries. + current_routing_options = std::make_shared()]( google::storage::v2::BidiWriteObjectRequest req) { - auto call = [stub, request = std::move(req)]( + auto call = [stub, request = std::move(req), current_routing_options]( CompletionQueue& cq, std::shared_ptr context, google::cloud::internal::ImmutableOptions options, @@ -351,9 +353,11 @@ AsyncConnectionImpl::AppendableObjectUploadImpl(AppendableUploadParams p) { // Apply the routing header if (request.has_write_object_spec()) - ApplyRoutingHeaders(*context, request.write_object_spec()); + ApplyRoutingHeaders(*context, request.write_object_spec(), + *current_routing_options); else - ApplyRoutingHeaders(*context, request.append_object_spec()); + ApplyRoutingHeaders(*context, request.append_object_spec(), + *current_routing_options); auto rpc = stub->AsyncBidiWriteObject(cq, std::move(context), std::move(options)); @@ -362,18 +366,30 @@ AsyncConnectionImpl::AppendableObjectUploadImpl(AppendableUploadParams p) { std::move(rpc)); request.set_state_lookup(true); auto open = std::make_shared(std::move(rpc), request); - return open->Call().then([open, &request](auto f) mutable { - open.reset(); - auto response = f.get(); - if (!response) { - google::rpc::Status grpc_status = - ExtractGrpcStatus(response.status()); - EnsureFirstMessageAppendObjectSpec(request, grpc_status); - ApplyWriteRedirectErrors(*request.mutable_append_object_spec(), - grpc_status); - } - return response; - }); + return open->Call().then( + [open, &request, current_routing_options](auto f) mutable { + open.reset(); + auto response = f.get(); + if (!response) { + google::rpc::Status grpc_status = + ExtractGrpcStatus(response.status()); + // Handle redirect and get info for updating routing options. + BidiWriteRedirectInfo redirect_info = + HandleBidiWriteRedirect(request, grpc_status); + + // Only update the routing token if the new info has a + // non-empty token. + // Otherwise, retain the existing token for subsequent + // retries. + if (!redirect_info.routing_token.empty() && + current_routing_options->routing_token != + redirect_info.routing_token) { + current_routing_options->routing_token = + redirect_info.routing_token; + } + } + return response; + }); }; return google::cloud::internal::AsyncRetryLoop( diff --git a/google/cloud/storage/internal/async/connection_impl_appendable_upload_test.cc b/google/cloud/storage/internal/async/connection_impl_appendable_upload_test.cc index 85ce51a10f31d..bb6833b1526a7 100644 --- a/google/cloud/storage/internal/async/connection_impl_appendable_upload_test.cc +++ b/google/cloud/storage/internal/async/connection_impl_appendable_upload_test.cc @@ -21,6 +21,8 @@ #include "google/cloud/common_options.h" #include "google/cloud/grpc_options.h" #include "google/cloud/internal/background_threads_impl.h" +#include "google/cloud/internal/status_payload_keys.h" +#include "google/cloud/status.h" #include "google/cloud/testing_util/async_sequencer.h" #include "google/cloud/testing_util/is_proto_equal.h" #include "google/cloud/testing_util/mock_completion_queue_impl.h" @@ -70,35 +72,14 @@ std::shared_ptr MakeTestConnection( TestOptions(std::move(options))); } -// Creates a mock bidirectional stream that simulates a successful append flow. -std::unique_ptr MakeSuccessfulAppendStream( +// Creates a mock bidirectional stream with common expectations for append +// flows. +std::unique_ptr MakeCommonAppendStream( AsyncSequencer& sequencer, std::int64_t persisted_size) { auto stream = std::make_unique(); EXPECT_CALL(*stream, Start).WillOnce([&] { return sequencer.PushBack("Start"); }); - // The first write is a "state lookup" write. It should not contain a payload. - // The server responds with the current persisted size of the object. - EXPECT_CALL(*stream, Write) - .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const& request, - grpc::WriteOptions wopt) { - EXPECT_TRUE(request.state_lookup()); - EXPECT_FALSE(wopt.is_last_message()); - return sequencer.PushBack("Write(StateLookup)"); - }) - // Subsequent writes carry data. - .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const&, - grpc::WriteOptions wopt) { - EXPECT_FALSE(wopt.is_last_message()); - return sequencer.PushBack("Write(data)"); - }) - // The finalize write marks the end of the stream. - .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const& request, - grpc::WriteOptions wopt) { - EXPECT_TRUE(request.finish_write()); - EXPECT_TRUE(wopt.is_last_message()); - return sequencer.PushBack("Write(Finalize)"); - }); // The first `Read()` call after the state lookup confirms the persisted size. EXPECT_CALL(*stream, Read) @@ -129,8 +110,111 @@ std::unique_ptr MakeSuccessfulAppendStream( EXPECT_CALL(*stream, Finish).WillOnce([&] { return sequencer.PushBack("Finish").then([](auto) { return Status{}; }); }); + return stream; +} - return std::unique_ptr(std::move(stream)); +std::unique_ptr MakeRedirectAppendStream( + AsyncSequencer& sequencer, std::int64_t persisted_size, + absl::string_view expected_handle, std::int64_t expected_generation, + absl::string_view expected_routing_token) { + auto stream = MakeCommonAppendStream(sequencer, persisted_size); + // The first write is a "state lookup" write. It should not contain a payload. + // The server responds with the current persisted size of the object. + EXPECT_CALL(*stream, Write) + .WillOnce([&sequencer, expected_handle, expected_generation, + expected_routing_token]( + google::storage::v2::BidiWriteObjectRequest const& request, + grpc::WriteOptions wopt) { + EXPECT_TRUE(request.state_lookup()); + EXPECT_FALSE(wopt.is_last_message()); + EXPECT_TRUE(request.has_append_object_spec()); + EXPECT_EQ(request.append_object_spec().write_handle().handle(), + expected_handle); + EXPECT_EQ(request.append_object_spec().generation(), + expected_generation); + EXPECT_EQ(request.append_object_spec().routing_token(), + expected_routing_token); + return sequencer.PushBack("Write(StateLookup)"); + }) + // Subsequent writes carry data. + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const&, + grpc::WriteOptions wopt) { + EXPECT_FALSE(wopt.is_last_message()); + return sequencer.PushBack("Write(data)"); + }) + // The finalize write marks the end of the stream. + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const& request, + grpc::WriteOptions wopt) { + EXPECT_TRUE(request.finish_write()); + EXPECT_TRUE(wopt.is_last_message()); + return sequencer.PushBack("Write(Finalize)"); + }); + return stream; +} + +std::unique_ptr MakeRedirectAppendStreamNoHandle( + AsyncSequencer& sequencer, std::int64_t persisted_size, + absl::string_view expected_bucket, absl::string_view expected_object_name) { + auto stream = MakeCommonAppendStream(sequencer, persisted_size); + // The first write is a "state lookup" write. It should not contain a payload. + // The server responds with the current persisted size of the object. + EXPECT_CALL(*stream, Write) + .WillOnce([&sequencer, expected_bucket, expected_object_name]( + google::storage::v2::BidiWriteObjectRequest const& request, + grpc::WriteOptions wopt) { + EXPECT_TRUE(request.state_lookup()); + EXPECT_FALSE(wopt.is_last_message()); + EXPECT_FALSE(request.has_append_object_spec()); + EXPECT_TRUE(request.has_write_object_spec()); + EXPECT_EQ(request.write_object_spec().resource().name(), + expected_object_name); + EXPECT_EQ(request.write_object_spec().resource().bucket(), + expected_bucket); + return sequencer.PushBack("Write(StateLookup)"); + }) + // Subsequent writes carry data. + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const&, + grpc::WriteOptions wopt) { + EXPECT_FALSE(wopt.is_last_message()); + return sequencer.PushBack("Write(data)"); + }) + // The finalize write marks the end of the stream. + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const& request, + grpc::WriteOptions wopt) { + EXPECT_TRUE(request.finish_write()); + EXPECT_TRUE(wopt.is_last_message()); + return sequencer.PushBack("Write(Finalize)"); + }); + return stream; +} + +// Creates a mock bidirectional stream that simulates a successful append flow. +std::unique_ptr MakeSuccessfulAppendStream( + AsyncSequencer& sequencer, std::int64_t persisted_size) { + auto stream = MakeCommonAppendStream(sequencer, persisted_size); + // The first write is a "state lookup" write. It should not contain a payload. + // The server responds with the current persisted size of the object. + EXPECT_CALL(*stream, Write) + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const& request, + grpc::WriteOptions wopt) { + EXPECT_TRUE(request.state_lookup()); + EXPECT_FALSE(wopt.is_last_message()); + return sequencer.PushBack("Write(StateLookup)"); + }) + // Subsequent writes carry data. + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const&, + grpc::WriteOptions wopt) { + EXPECT_FALSE(wopt.is_last_message()); + return sequencer.PushBack("Write(data)"); + }) + // The finalize write marks the end of the stream. + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const& request, + grpc::WriteOptions wopt) { + EXPECT_TRUE(request.finish_write()); + EXPECT_TRUE(wopt.is_last_message()); + return sequencer.PushBack("Write(Finalize)"); + }); + return stream; } // Creates a mock stream that returns an error. @@ -348,6 +432,201 @@ TEST_F(AsyncConnectionImplAppendableTest, AppendableUploadPermanentError) { EXPECT_THAT(r, StatusIs(PermanentError().code())); } +TEST_F(AsyncConnectionImplAppendableTest, AppendableUploadRedirect) { + auto constexpr kRequestText = R"pb( + write_object_spec { + resource { + bucket: "projects/_/buckets/test-bucket" + name: "test-object" + content_type: "text/plain" + } + } + )pb"; + AsyncSequencer sequencer; + auto mock = std::make_shared(); + + google::rpc::Status rpc_status; + rpc_status.set_code(static_cast(StatusCode::kAborted)); + rpc_status.set_message("redirect"); + google::storage::v2::BidiWriteObjectRedirectedError redirect; + redirect.mutable_write_handle()->set_handle("redirect-handle"); + redirect.set_routing_token("redirect-token"); + redirect.set_generation(4321); + rpc_status.add_details()->PackFrom(redirect); + std::string rpc_status_payload; + ASSERT_TRUE(rpc_status.SerializeToString(&rpc_status_payload)); + Status status(StatusCode::kAborted, "redirect"); + internal::SetPayload(status, internal::StatusPayloadGrpcProto(), + rpc_status_payload); + + // Simulate one redirect failure, followed by a success. + EXPECT_CALL(*mock, AsyncBidiWriteObject) + .WillOnce([&] { return MakeErrorBidiWriteStream(sequencer, status); }) + .WillOnce([&] { + return MakeRedirectAppendStream(sequencer, 1024, "redirect-handle", + 4321, "redirect-token"); + }); + + internal::AutomaticallyCreatedBackgroundThreads pool(1); + auto connection = MakeTestConnection(pool.cq(), mock); + + auto request = google::storage::v2::BidiWriteObjectRequest{}; + ASSERT_TRUE(TextFormat::ParseFromString(kRequestText, &request)); + auto pending = connection->StartAppendableObjectUpload( + {std::move(request), connection->options()}); + + // First attempt fails with redirect. + auto next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Start"); + next.first.set_value(false); // The stream fails to start. + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Finish"); + next.first.set_value(true); + + // Retry attempt succeeds. + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Start"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Write(StateLookup)"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Read(PersistedSize)"); + next.first.set_value(true); + + auto r = pending.get(); + ASSERT_STATUS_OK(r); + auto writer = *std::move(r); + EXPECT_EQ(absl::get(writer->PersistedState()), 1024); + + // Write some data. + auto w1 = writer->Write(storage::WritePayload("some data")); + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Write(data)"); + next.first.set_value(true); + EXPECT_STATUS_OK(w1.get()); + + // Finalize the upload. + auto w2 = writer->Finalize({}); + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Write(Finalize)"); + next.first.set_value(true); + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Read(FinalObject)"); + next.first.set_value(true); + + auto response = w2.get(); + ASSERT_STATUS_OK(response); + EXPECT_EQ(response->bucket(), "projects/_/buckets/test-bucket"); + EXPECT_EQ(response->name(), "test-object"); + EXPECT_EQ(response->size(), 1024 + 1024); + + writer.reset(); + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Finish"); + next.first.set_value(true); +} + +TEST_F(AsyncConnectionImplAppendableTest, AppendableUploadRedirectNoHandle) { + auto constexpr kRequestText = R"pb( + write_object_spec { + resource { + bucket: "projects/_/buckets/test-bucket" + name: "test-object" + content_type: "text/plain" + } + } + )pb"; + AsyncSequencer sequencer; + auto mock = std::make_shared(); + + google::rpc::Status rpc_status; + rpc_status.set_code(static_cast(StatusCode::kAborted)); + rpc_status.set_message("redirect"); + google::storage::v2::BidiWriteObjectRedirectedError redirect; + redirect.set_routing_token("redirect-token"); + redirect.set_generation(4321); + rpc_status.add_details()->PackFrom(redirect); + std::string rpc_status_payload; + ASSERT_TRUE(rpc_status.SerializeToString(&rpc_status_payload)); + Status status(StatusCode::kAborted, "redirect"); + internal::SetPayload(status, internal::StatusPayloadGrpcProto(), + rpc_status_payload); + + // Simulate one redirect failure, followed by a success. + EXPECT_CALL(*mock, AsyncBidiWriteObject) + .WillOnce([&] { return MakeErrorBidiWriteStream(sequencer, status); }) + .WillOnce([&] { + return MakeRedirectAppendStreamNoHandle( + sequencer, 1024, "projects/_/buckets/test-bucket", "test-object"); + }); + + internal::AutomaticallyCreatedBackgroundThreads pool(1); + auto connection = MakeTestConnection(pool.cq(), mock); + + auto request = google::storage::v2::BidiWriteObjectRequest{}; + ASSERT_TRUE(TextFormat::ParseFromString(kRequestText, &request)); + auto pending = connection->StartAppendableObjectUpload( + {std::move(request), connection->options()}); + + // First attempt fails with redirect. + auto next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Start"); + next.first.set_value(false); // The stream fails to start. + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Finish"); + next.first.set_value(true); + + // Retry attempt succeeds. + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Start"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Write(StateLookup)"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Read(PersistedSize)"); + next.first.set_value(true); + + auto r = pending.get(); + ASSERT_STATUS_OK(r); + auto writer = *std::move(r); + EXPECT_EQ(absl::get(writer->PersistedState()), 1024); + + // Write some data. + auto w1 = writer->Write(storage::WritePayload("some data")); + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Write(data)"); + next.first.set_value(true); + EXPECT_STATUS_OK(w1.get()); + + // Finalize the upload. + auto w2 = writer->Finalize({}); + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Write(Finalize)"); + next.first.set_value(true); + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Read(FinalObject)"); + next.first.set_value(true); + + auto response = w2.get(); + ASSERT_STATUS_OK(response); + EXPECT_EQ(response->bucket(), "projects/_/buckets/test-bucket"); + EXPECT_EQ(response->name(), "test-object"); + EXPECT_EQ(response->size(), 1024 + 1024); + + writer.reset(); + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Finish"); + next.first.set_value(true); +} + } // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage_internal diff --git a/google/cloud/storage/internal/async/handle_redirect_error.cc b/google/cloud/storage/internal/async/handle_redirect_error.cc index 1444a8bda6f19..d8fe1ad0684c4 100644 --- a/google/cloud/storage/internal/async/handle_redirect_error.cc +++ b/google/cloud/storage/internal/async/handle_redirect_error.cc @@ -20,27 +20,6 @@ namespace cloud { namespace storage_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN -void EnsureFirstMessageAppendObjectSpec( - google::storage::v2::BidiWriteObjectRequest& request, - google::rpc::Status const& rpc_status) { - for (auto const& rpc_status_detail : rpc_status.details()) { - google::storage::v2::BidiWriteObjectRedirectedError error = - google::storage::v2::BidiWriteObjectRedirectedError{}; - if (!rpc_status_detail.UnpackTo(&error)) continue; - if (!error.has_write_handle()) continue; - if (request.has_write_object_spec()) { - auto spec = request.write_object_spec(); - auto& append_object_spec = *request.mutable_append_object_spec(); - append_object_spec.set_bucket(spec.resource().bucket()); - append_object_spec.set_object(spec.resource().name()); - append_object_spec.set_if_metageneration_match( - spec.if_metageneration_match()); - append_object_spec.set_if_metageneration_not_match( - spec.if_metageneration_not_match()); - } - } -} - google::rpc::Status ExtractGrpcStatus(Status const& status) { google::rpc::Status proto_status = google::rpc::Status{}; auto payload = google::cloud::internal::GetPayload( @@ -72,6 +51,59 @@ void ApplyWriteRedirectErrors(google::storage::v2::AppendObjectSpec& spec, } } +BidiWriteRedirectInfo HandleBidiWriteRedirect( + google::storage::v2::BidiWriteObjectRequest& request, + google::rpc::Status const& rpc_status) { + BidiWriteRedirectInfo info; + std::optional + redirect_error; + for (auto const& rpc_status_detail : rpc_status.details()) { + google::storage::v2::BidiWriteObjectRedirectedError error_proto; + if (rpc_status_detail.UnpackTo(&error_proto)) { + redirect_error = std::move(error_proto); + break; // Found the redirect error, no need to look further. + } + } + if (!redirect_error) { + return info; + } + + // We always extract the routing token if it's provided, as it's needed for + // the x-goog-request-params header in the next retry attempt. + if (!redirect_error->routing_token().empty()) { + info.routing_token = redirect_error->routing_token(); + } + if (!redirect_error->has_write_handle()) { + return info; + } + + // If we get back a write handle, we should use it. We can only use it + // on an append object spec. If we have a write object spec, we copy the + // relevant fields from write object spec to append object spec. + // If we have an append object spec, we copy the relevant fields from the + // error to the spec. + if (request.has_write_object_spec()) { + auto write_object_spec = request.write_object_spec(); + auto& append_object_spec = *request.mutable_append_object_spec(); + append_object_spec.set_bucket(write_object_spec.resource().bucket()); + append_object_spec.set_object(write_object_spec.resource().name()); + append_object_spec.set_if_metageneration_match( + write_object_spec.if_metageneration_match()); + append_object_spec.set_if_metageneration_not_match( + write_object_spec.if_metageneration_not_match()); + } + if (request.has_append_object_spec()) { + auto& append_object_spec = *request.mutable_append_object_spec(); + *append_object_spec.mutable_write_handle() = + std::move(*redirect_error->mutable_write_handle()); + *append_object_spec.mutable_routing_token() = + std::move(*redirect_error->mutable_routing_token()); + if (redirect_error->has_generation()) + append_object_spec.set_generation(redirect_error->generation()); + } + return info; +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage_internal } // namespace cloud diff --git a/google/cloud/storage/internal/async/handle_redirect_error.h b/google/cloud/storage/internal/async/handle_redirect_error.h index 5cdf3b1b3d181..29d2a6025c3ec 100644 --- a/google/cloud/storage/internal/async/handle_redirect_error.h +++ b/google/cloud/storage/internal/async/handle_redirect_error.h @@ -25,9 +25,10 @@ namespace cloud { namespace storage_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN -void EnsureFirstMessageAppendObjectSpec( - google::storage::v2::BidiWriteObjectRequest& request, - google::rpc::Status const& rpc_status); +struct BidiWriteRedirectInfo { + // The routing token extracted from the redirect error, if available. + std::string routing_token; +}; google::rpc::Status ExtractGrpcStatus(Status const& status); @@ -37,6 +38,12 @@ void ApplyRedirectErrors(google::storage::v2::BidiReadObjectSpec& spec, void ApplyWriteRedirectErrors(google::storage::v2::AppendObjectSpec& spec, google::rpc::Status const& rpc_status); +// Handles BidiWriteObjectRedirectedError and modifies the request accordingly. +// Returns information needed for the next retry attempt. +BidiWriteRedirectInfo HandleBidiWriteRedirect( + google::storage::v2::BidiWriteObjectRequest& request, + google::rpc::Status const& rpc_status); + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage_internal } // namespace cloud diff --git a/google/cloud/storage/internal/async/handle_redirect_error_test.cc b/google/cloud/storage/internal/async/handle_redirect_error_test.cc index d681cdf1e790c..b3ecca5bb5073 100644 --- a/google/cloud/storage/internal/async/handle_redirect_error_test.cc +++ b/google/cloud/storage/internal/async/handle_redirect_error_test.cc @@ -70,58 +70,150 @@ TEST(ApplyRedirectErrors, NoRedirect) { EXPECT_TRUE(spec.routing_token().empty()); } -TEST(EnsureFirstMessageAppendObjectSpec, Success) { +TEST(ApplyWriteRedirectErrors, NoRedirect) { + google::storage::v2::AppendObjectSpec spec; + spec.set_bucket("projects/_/buckets/test-bucket"); + spec.set_object("test-object"); + google::rpc::Status rpc_status; + rpc_status.set_code(static_cast(StatusCode::kNotFound)); + rpc_status.set_message("test-message"); + + ApplyWriteRedirectErrors(spec, rpc_status); + EXPECT_EQ(spec.bucket(), "projects/_/buckets/test-bucket"); + EXPECT_EQ(spec.object(), "test-object"); + EXPECT_FALSE(spec.has_write_handle()); + EXPECT_TRUE(spec.routing_token().empty()); + EXPECT_EQ(spec.generation(), 0); +} + +TEST(ApplyWriteRedirectErrors, Success) { + google::storage::v2::AppendObjectSpec spec; + spec.set_bucket("projects/_/buckets/test-bucket"); + spec.set_object("test-object"); + google::rpc::Status rpc_status; + google::storage::v2::BidiWriteObjectRedirectedError redirect; + redirect.mutable_write_handle()->set_handle("test-handle"); + redirect.set_routing_token("test-token"); + redirect.set_generation(1234); + rpc_status.add_details()->PackFrom(redirect); + + ApplyWriteRedirectErrors(spec, rpc_status); + EXPECT_EQ(spec.bucket(), "projects/_/buckets/test-bucket"); + EXPECT_EQ(spec.object(), "test-object"); + EXPECT_EQ(spec.write_handle().handle(), "test-handle"); + EXPECT_EQ(spec.routing_token(), "test-token"); + EXPECT_EQ(spec.generation(), 1234); +} + +TEST(HandleBidiWriteRedirect, NoRedirect) { + google::storage::v2::BidiWriteObjectRequest request; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb( + write_object_spec { + resource { bucket: "projects/_/buckets/b", name: "o" } + } + )pb", + &request)); + + google::rpc::Status rpc_status; + rpc_status.set_code(static_cast(StatusCode::kNotFound)); + rpc_status.set_message("test-message"); + + auto info = HandleBidiWriteRedirect(request, rpc_status); + EXPECT_TRUE(info.routing_token.empty()); + EXPECT_TRUE(request.has_write_object_spec()); + EXPECT_FALSE(request.has_append_object_spec()); +} + +TEST(HandleBidiWriteRedirect, NoWriteHandle) { google::storage::v2::BidiWriteObjectRequest request; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( R"pb( write_object_spec { resource { bucket: "projects/_/buckets/b", name: "o" } - if_metageneration_match: 1 - if_metageneration_not_match: 1 } )pb", &request)); google::rpc::Status rpc_status; google::storage::v2::BidiWriteObjectRedirectedError redirect; - redirect.mutable_write_handle(); + redirect.set_routing_token("test-token"); rpc_status.add_details()->PackFrom(redirect); - EnsureFirstMessageAppendObjectSpec(request, rpc_status); + auto info = HandleBidiWriteRedirect(request, rpc_status); + EXPECT_EQ(info.routing_token, "test-token"); + EXPECT_TRUE(request.has_write_object_spec()); + EXPECT_FALSE(request.has_append_object_spec()); +} +TEST(HandleBidiWriteRedirect, WithWriteHandleForWriteObjectSpec) { + google::storage::v2::BidiWriteObjectRequest request; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb( + write_object_spec { + resource { bucket: "projects/_/buckets/b", name: "o" } + if_metageneration_match: 1 + if_metageneration_not_match: 2 + } + )pb", + &request)); + + google::rpc::Status rpc_status; + google::storage::v2::BidiWriteObjectRedirectedError redirect; + redirect.mutable_write_handle()->set_handle("test-handle"); + redirect.set_routing_token("test-token"); + redirect.set_generation(1234); + rpc_status.add_details()->PackFrom(redirect); + + auto info = HandleBidiWriteRedirect(request, rpc_status); + EXPECT_EQ(info.routing_token, "test-token"); EXPECT_FALSE(request.has_write_object_spec()); EXPECT_TRUE(request.has_append_object_spec()); - auto const& append_spec = request.append_object_spec(); - EXPECT_EQ(append_spec.bucket(), "projects/_/buckets/b"); - EXPECT_EQ(append_spec.object(), "o"); - - EXPECT_FALSE(append_spec.has_write_handle()); - EXPECT_TRUE(append_spec.routing_token().empty()); - EXPECT_EQ(append_spec.if_metageneration_match(), 1); - EXPECT_EQ(append_spec.if_metageneration_not_match(), 1); - EXPECT_EQ(append_spec.generation(), 0); + auto const& spec = request.append_object_spec(); + EXPECT_EQ(spec.bucket(), "projects/_/buckets/b"); + EXPECT_EQ(spec.object(), "o"); + EXPECT_EQ(spec.if_metageneration_match(), 1); + EXPECT_EQ(spec.if_metageneration_not_match(), 2); + EXPECT_EQ(spec.write_handle().handle(), "test-handle"); + EXPECT_EQ(spec.routing_token(), "test-token"); + EXPECT_EQ(spec.generation(), 1234); } -TEST(EnsureFirstMessageAppendObjectSpec, WriteHandleIsNotSet) { +TEST(HandleBidiWriteRedirect, WithWriteHandleForAppendObjectSpec) { google::storage::v2::BidiWriteObjectRequest request; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( R"pb( - write_object_spec { - resource { bucket: "projects/_/buckets/b", name: "o" } + append_object_spec { + bucket: "projects/_/buckets/b" + object: "o" + generation: 123 + if_metageneration_match: 1 + if_metageneration_not_match: 2 } )pb", &request)); google::rpc::Status rpc_status; google::storage::v2::BidiWriteObjectRedirectedError redirect; + redirect.mutable_write_handle()->set_handle("test-handle"); + redirect.set_routing_token("test-token"); redirect.set_generation(1234); rpc_status.add_details()->PackFrom(redirect); - EnsureFirstMessageAppendObjectSpec(request, rpc_status); + auto info = HandleBidiWriteRedirect(request, rpc_status); + EXPECT_EQ(info.routing_token, "test-token"); + EXPECT_FALSE(request.has_write_object_spec()); + EXPECT_TRUE(request.has_append_object_spec()); - EXPECT_TRUE(request.has_write_object_spec()); - EXPECT_FALSE(request.has_append_object_spec()); + auto const& spec = request.append_object_spec(); + EXPECT_EQ(spec.bucket(), "projects/_/buckets/b"); + EXPECT_EQ(spec.object(), "o"); + EXPECT_EQ(spec.if_metageneration_match(), 1); + EXPECT_EQ(spec.if_metageneration_not_match(), 2); + EXPECT_EQ(spec.write_handle().handle(), "test-handle"); + EXPECT_EQ(spec.routing_token(), "test-token"); + EXPECT_EQ(spec.generation(), 1234); } } // namespace diff --git a/google/cloud/storage/internal/async/writer_connection_buffered.cc b/google/cloud/storage/internal/async/writer_connection_buffered.cc index 786a33b3b89cd..ec179e207b516 100644 --- a/google/cloud/storage/internal/async/writer_connection_buffered.cc +++ b/google/cloud/storage/internal/async/writer_connection_buffered.cc @@ -281,7 +281,8 @@ class AsyncWriterConnectionBufferedState return tmp; } - void OnQuery(std::unique_lock lk, std::int64_t persisted_size) { + void OnQuery(std::unique_lock lk, std::int64_t persisted_size, + bool is_resume = false) { if (persisted_size < buffer_offset_) { auto id = UploadId(lk); return SetError(std::move(lk), @@ -297,7 +298,22 @@ class AsyncWriterConnectionBufferedState } resend_buffer_.RemovePrefix(static_cast(n)); buffer_offset_ = persisted_size; - write_offset_ -= static_cast(n); + if (is_resume) { + // Since the buffer has been modified to start exactly at the point of the + // resume, the next write on this new stream should start from the + // beginning of this truncated buffer. + write_offset_ = 0; + } else { + // While rare, it is possible that n >= write_offset_ (i.e. the server has + // persisted more than we have sent) if, for example, multiple clients + // resume the same upload. If that is the case, all the bytes covered by + // write_offset_ have been flushed and we can reset it to 0. + if (static_cast(n) >= write_offset_) { + write_offset_ = 0; + } else { + write_offset_ -= static_cast(n); + } + } // If the buffer is small enough, collect all the handlers to notify them. auto const handlers = ClearHandlersIfEmpty(lk); // SetFlushed will release the lock before returning. @@ -382,7 +398,7 @@ class AsyncWriterConnectionBufferedState std::move(state))); } // Regular resume succeeded, object not finalized. Continue writing. - OnQuery(std::move(lk), absl::get(state)); + OnQuery(std::move(lk), absl::get(state), /*is_resume=*/true); } void SetFinalized(std::unique_lock lk, diff --git a/google/cloud/storage/internal/async/writer_connection_buffered_test.cc b/google/cloud/storage/internal/async/writer_connection_buffered_test.cc index 40758de3c17e6..ed01b4581a3f0 100644 --- a/google/cloud/storage/internal/async/writer_connection_buffered_test.cc +++ b/google/cloud/storage/internal/async/writer_connection_buffered_test.cc @@ -1266,6 +1266,67 @@ TEST(WriteConnectionBuffered, SetFinalizedIsIdempotent) { next.first.set_value(true); } +TEST(WriteConnectionBuffered, ResetWriteOffsetOnResume) { + AsyncSequencer sequencer; + auto mock = std::make_unique(); + auto* mock_ptr = mock.get(); + + EXPECT_CALL(*mock_ptr, UploadId).WillRepeatedly(Return("test-upload-id")); + EXPECT_CALL(*mock_ptr, PersistedState) + .WillOnce( + Return(MakePersistedState(0))); // Initial state: 0 bytes persisted. + + EXPECT_CALL(*mock_ptr, Write).WillOnce([&](auto) { + return sequencer.PushBack("Write").then([](auto f) { + if (!f.get()) return TransientError(); // This write will fail. + return Status{}; + }); + }); + + MockFactory mock_factory; + auto resumed_mock = std::make_unique(); + auto* resumed_mock_ptr = resumed_mock.get(); + + EXPECT_CALL(mock_factory, Call).WillOnce([&]() { + return sequencer.PushBack("Resume").then([&](auto) { + // The resumed connection reports that 1024 bytes have been persisted. + EXPECT_CALL(*resumed_mock_ptr, PersistedState) + .WillRepeatedly(Return(MakePersistedState(1024))); + // We expect the next write on the resumed stream to send the remaining + // 1024 bytes. If the write offset was not reset to 0, this size would be + // incorrect. + EXPECT_CALL(*resumed_mock_ptr, Write).WillOnce([&](auto payload) { + EXPECT_EQ(payload.size(), 1024); + return sequencer.PushBack("ResumedWrite").then([](auto) { + return Status{}; + }); + }); + return make_status_or(std::unique_ptr( + std::move(resumed_mock))); + }); + }); + + auto connection = MakeWriterConnectionBuffered( + mock_factory.AsStdFunction(), std::move(mock), TestOptions()); + + // Write a total of 2048 bytes. + auto write = connection->Write(TestPayload(2048)); + + auto next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Write"); + next.first.set_value(false); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Resume"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "ResumedWrite"); + next.first.set_value(true); + + EXPECT_STATUS_OK(write.get()); +} + } // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage_internal diff --git a/google/cloud/storage/internal/async/writer_connection_impl.cc b/google/cloud/storage/internal/async/writer_connection_impl.cc index cbc4087169fbb..8e579cfe64cd7 100644 --- a/google/cloud/storage/internal/async/writer_connection_impl.cc +++ b/google/cloud/storage/internal/async/writer_connection_impl.cc @@ -237,9 +237,7 @@ future> AsyncWriterConnectionImpl::OnQuery( .then([this](auto g) { auto result = g.get(); google::rpc::Status grpc_status = ExtractGrpcStatus(result); - EnsureFirstMessageAppendObjectSpec(request_, grpc_status); - ApplyWriteRedirectErrors(*request_.mutable_append_object_spec(), - grpc_status); + HandleBidiWriteRedirect(request_, grpc_status); return StatusOr(std::move(result)); }); } diff --git a/google/cloud/storage/internal/async/writer_connection_impl_test.cc b/google/cloud/storage/internal/async/writer_connection_impl_test.cc index 037954d4a153a..caaa3d4ae8d1c 100644 --- a/google/cloud/storage/internal/async/writer_connection_impl_test.cc +++ b/google/cloud/storage/internal/async/writer_connection_impl_test.cc @@ -21,6 +21,8 @@ #include "google/cloud/storage/options.h" #include "google/cloud/storage/testing/canonical_errors.h" #include "google/cloud/storage/testing/mock_hash_function.h" +#include "google/cloud/internal/status_payload_keys.h" +#include "google/cloud/status.h" #include "google/cloud/testing_util/async_sequencer.h" #include "google/cloud/testing_util/is_proto_equal.h" #include "google/cloud/testing_util/status_matchers.h" @@ -649,6 +651,49 @@ TEST(AsyncWriterConnectionTest, UnexpectedQueryFailsWithoutError) { EXPECT_THAT(query.get(), StatusIs(StatusCode::kInternal)); } +TEST(AsyncWriterConnectionTest, QueryFailsWithRedirect) { + AsyncSequencer sequencer; + auto mock = std::make_unique(); + EXPECT_CALL(*mock, Cancel).Times(1); + EXPECT_CALL(*mock, Read).WillOnce([&]() { + return sequencer.PushBack("Read").then( + [](auto) { return absl::optional(); }); + }); + + google::rpc::Status rpc_status; + rpc_status.set_code(static_cast(StatusCode::kAborted)); + rpc_status.set_message("redirect"); + google::storage::v2::BidiWriteObjectRedirectedError redirect; + redirect.mutable_write_handle()->set_handle("redirect-handle"); + redirect.set_routing_token("redirect-token"); + redirect.set_generation(4321); + rpc_status.add_details()->PackFrom(redirect); + std::string rpc_status_payload; + ASSERT_TRUE(rpc_status.SerializeToString(&rpc_status_payload)); + Status status(StatusCode::kAborted, "redirect"); + internal::SetPayload(status, internal::StatusPayloadGrpcProto(), + rpc_status_payload); + + EXPECT_CALL(*mock, Finish).WillOnce([&, status] { + return sequencer.PushBack("Finish").then([s = status](auto f) -> Status { + if (f.get()) return Status{}; + return s; + }); + }); + auto hash = std::make_shared(); + + auto tested = std::make_unique( + TestOptions(), MakeRequest(), std::move(mock), hash, 1024); + auto query = tested->Query(); + auto next = sequencer.PopFrontWithName(); + ASSERT_THAT(next.second, "Read"); + next.first.set_value(false); // Detect error from Read() + next = sequencer.PopFrontWithName(); + ASSERT_THAT(next.second, "Finish"); + next.first.set_value(false); // Return error from Finish() + EXPECT_THAT(query.get(), StatusIs(StatusCode::kAborted)); +} + TEST(AsyncWriterConnectionTest, FinalizeAppendableNoChecksum) { AsyncSequencer sequencer; auto mock = std::make_unique(); diff --git a/google/cloud/storage/internal/async/writer_connection_resumed.cc b/google/cloud/storage/internal/async/writer_connection_resumed.cc index 3b860bef02a6e..5ae78d307bd59 100644 --- a/google/cloud/storage/internal/async/writer_connection_resumed.cc +++ b/google/cloud/storage/internal/async/writer_connection_resumed.cc @@ -317,7 +317,22 @@ class AsyncWriterConnectionResumedState } resend_buffer_.RemovePrefix(static_cast(n)); buffer_offset_ = persisted_size; - write_offset_ -= static_cast(n); + if (state_ == State::kResuming) { + // Since the buffer has been modified to start exactly at the point of the + // resume, the next write on this new stream should start from the + // beginning of this truncated buffer. + write_offset_ = 0; + } else { + // While rare, it is possible that n >= write_offset_ (i.e. the server has + // persisted more than we have sent) if, for example, multiple clients + // resume the same upload. If that is the case, all the bytes covered by + // write_offset_ have been flushed and we can reset it to 0. + if (static_cast(n) >= write_offset_) { + write_offset_ = 0; + } else { + write_offset_ -= static_cast(n); + } + } // If the buffer is small enough, collect all the handlers to notify them. auto const handlers = ClearHandlersIfEmpty(lk); state_ = State::kIdle; diff --git a/google/cloud/storage/internal/async/writer_connection_resumed_test.cc b/google/cloud/storage/internal/async/writer_connection_resumed_test.cc index c274b683c4287..dcebb80ac8c63 100644 --- a/google/cloud/storage/internal/async/writer_connection_resumed_test.cc +++ b/google/cloud/storage/internal/async/writer_connection_resumed_test.cc @@ -11,9 +11,10 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - #include "google/cloud/storage/internal/async/writer_connection_resumed.h" +#include "google/cloud/mocks/mock_async_streaming_read_write_rpc.h" #include "google/cloud/storage/async/connection.h" +#include "google/cloud/storage/internal/grpc/ctype_cord_workaround.h" #include "google/cloud/storage/mocks/mock_async_writer_connection.h" #include "google/cloud/storage/testing/canonical_errors.h" #include "google/cloud/storage/testing/mock_hash_function.h" @@ -615,6 +616,123 @@ TEST(WriterConnectionResumed, OnQueryUpdatesWriteHandle) { EXPECT_EQ(current_handle->handle(), "updated-handle"); } +TEST(WriterConnectionResumed, ResetWriteOffsetOnResume) { + AsyncSequencer sequencer; + auto mock = std::make_unique(); + auto* mock_ptr = mock.get(); + + auto initial_request = google::storage::v2::BidiWriteObjectRequest{}; + google::storage::v2::BidiWriteObjectResponse first_response; + first_response.mutable_write_handle()->set_handle("initial-handle"); + + auto mock_hash = + std::make_shared(); + EXPECT_CALL(*mock_hash, Update(::testing::An(), + ::testing::An(), + ::testing::An())) + .WillRepeatedly(Return(Status())); + + EXPECT_CALL(*mock_ptr, PersistedState) + .WillOnce(Return(MakePersistedState(0))) + .WillOnce(Return(MakePersistedState(1024))); + + auto const payload = TestPayload(2048); + + EXPECT_CALL(*mock_ptr, Flush(_)).WillOnce([&](auto) { + return sequencer.PushBack("Flush").then([](auto f) { + if (f.get()) return Status{}; + return TransientError(); + }); + }); + + MockFactory mock_factory; + auto mock_stream = + std::make_unique>(); + auto* mock_stream_ptr = mock_stream.get(); + + EXPECT_CALL(mock_factory, Call(_)) + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const&) { + WriteObject::WriteResult result; + result.stream = std::move(mock_stream); + result.first_response.mutable_write_handle()->set_handle("new-handle"); + return sequencer.PushBack("Factory").then( + [r = std::move(result)](auto) mutable { + return StatusOr(std::move(r)); + }); + }); + + EXPECT_CALL(*mock_stream_ptr, Write(_, _)) + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const& request, + grpc::WriteOptions) { + EXPECT_EQ(GetContent(request.checksummed_data()).size(), 1024); + EXPECT_EQ(GetContent(request.checksummed_data()), + std::string(1024, 'A')); + return sequencer.PushBack("StreamWrite").then([](auto) { + return true; + }); + }) + .WillOnce([&](google::storage::v2::BidiWriteObjectRequest const& request, + grpc::WriteOptions) { + EXPECT_TRUE(GetContent(request.checksummed_data()).empty()); + EXPECT_TRUE(request.flush()); + return sequencer.PushBack("GhostWrite").then([](auto) { return true; }); + }); + + google::storage::v2::BidiWriteObjectResponse read_response1; + read_response1.set_persisted_size(2048); + google::storage::v2::BidiWriteObjectResponse read_response2; + read_response2.set_persisted_size(2048); + EXPECT_CALL(*mock_stream_ptr, Read) + .WillOnce([&, read_response1]() { + return sequencer.PushBack("StreamRead1").then([read_response1](auto) { + return absl::make_optional(read_response1); + }); + }) + .WillOnce([&, read_response2]() { + return sequencer.PushBack("StreamRead2").then([read_response2](auto) { + return absl::make_optional(read_response2); + }); + }); + + EXPECT_CALL(*mock_stream_ptr, Finish) + .WillOnce(Return(make_ready_future(Status{}))); + EXPECT_CALL(*mock_stream_ptr, Cancel).WillRepeatedly(Return()); + + auto connection = MakeWriterConnectionResumed( + mock_factory.AsStdFunction(), std::move(mock), initial_request, mock_hash, + first_response, Options{}); + + auto write = connection->Write(payload); + + auto next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Flush"); + next.first.set_value(false); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "Factory"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "StreamWrite"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "StreamRead1"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "GhostWrite"); + next.first.set_value(true); + + next = sequencer.PopFrontWithName(); + EXPECT_EQ(next.second, "StreamRead2"); + next.first.set_value(true); + + EXPECT_THAT(write.get(), StatusIs(StatusCode::kOk)); +} + } // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage_internal diff --git a/google/cloud/storage/internal/grpc/configure_client_context.cc b/google/cloud/storage/internal/grpc/configure_client_context.cc index ff716764e8ece..2d77f99dced76 100644 --- a/google/cloud/storage/internal/grpc/configure_client_context.cc +++ b/google/cloud/storage/internal/grpc/configure_client_context.cc @@ -20,6 +20,19 @@ namespace google { namespace cloud { namespace storage_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { +void ApplyRoutingHeadersImpl(grpc::ClientContext& context, + std::string const& bucket_name, + RoutingHeaderOptions const& options) { + std::string params = + "bucket=" + google::cloud::internal::UrlEncode(bucket_name); + if (!options.routing_token.empty()) { + params += "&routing_token=" + + google::cloud::internal::UrlEncode(options.routing_token); + } + context.AddMetadata("x-goog-request-params", std::move(params)); +} +} // namespace auto constexpr kIdempotencyTokenHeader = "x-goog-gcs-idempotency-token"; @@ -44,17 +57,15 @@ void ApplyRoutingHeaders( } void ApplyRoutingHeaders(grpc::ClientContext& context, - google::storage::v2::WriteObjectSpec const& spec) { - context.AddMetadata( - "x-goog-request-params", - "bucket=" + google::cloud::internal::UrlEncode(spec.resource().bucket())); + google::storage::v2::WriteObjectSpec const& spec, + RoutingHeaderOptions const& options) { + ApplyRoutingHeadersImpl(context, spec.resource().bucket(), options); } void ApplyRoutingHeaders(grpc::ClientContext& context, - google::storage::v2::AppendObjectSpec const& spec) { - context.AddMetadata( - "x-goog-request-params", - "bucket=" + google::cloud::internal::UrlEncode(spec.bucket())); + google::storage::v2::AppendObjectSpec const& spec, + RoutingHeaderOptions const& options) { + ApplyRoutingHeadersImpl(context, spec.bucket(), options); } void ApplyRoutingHeaders(grpc::ClientContext& context, diff --git a/google/cloud/storage/internal/grpc/configure_client_context.h b/google/cloud/storage/internal/grpc/configure_client_context.h index 7c898fbe9bbd6..bbc4a4c80d226 100644 --- a/google/cloud/storage/internal/grpc/configure_client_context.h +++ b/google/cloud/storage/internal/grpc/configure_client_context.h @@ -28,6 +28,11 @@ namespace cloud { namespace storage_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +struct RoutingHeaderOptions { + // The routing token to be included in x-goog-request-params. + std::string routing_token; +}; + /// Configures @p ctx using @p context. void AddIdempotencyToken(grpc::ClientContext& ctx, rest_internal::RestContext const& context); @@ -77,11 +82,13 @@ void ApplyRoutingHeaders( /// @copydoc ApplyRoutingHeaders(grpc::ClientContext&,) void ApplyRoutingHeaders(grpc::ClientContext& context, - google::storage::v2::WriteObjectSpec const& spec); + google::storage::v2::WriteObjectSpec const& spec, + RoutingHeaderOptions const& options = {}); /// @copydoc ApplyRoutingHeaders(grpc::ClientContext&,) void ApplyRoutingHeaders(grpc::ClientContext& context, - google::storage::v2::AppendObjectSpec const& spec); + google::storage::v2::AppendObjectSpec const& spec, + RoutingHeaderOptions const& options = {}); /** * The generated `StorageMetadata` stub can not handle dynamic routing headers diff --git a/google/cloud/storage/internal/grpc/configure_client_context_test.cc b/google/cloud/storage/internal/grpc/configure_client_context_test.cc index 550f956372e69..7e18790de74d3 100644 --- a/google/cloud/storage/internal/grpc/configure_client_context_test.cc +++ b/google/cloud/storage/internal/grpc/configure_client_context_test.cc @@ -156,6 +156,20 @@ TEST_F(GrpcConfigureClientContext, ApplyRoutingHeadersInsertObject) { "bucket=projects%2F_%2Fbuckets%2Ftest-bucket"))); } +TEST_F(GrpcConfigureClientContext, + ApplyRoutingHeadersInsertObjectWithRoutingToken) { + auto spec = google::storage::v2::WriteObjectSpec{}; + spec.mutable_resource()->set_bucket("projects/_/buckets/test-bucket"); + + grpc::ClientContext context; + ApplyRoutingHeaders(context, spec, {"test-token"}); + auto metadata = GetMetadata(context); + EXPECT_THAT(metadata, + Contains(Pair("x-goog-request-params", + "bucket=projects%2F_%2Fbuckets%2Ftest-bucket&" + "routing_token=test-token"))); +} + TEST_F(GrpcConfigureClientContext, ApplyRoutingHeadersUploadChunkMatchSlash) { storage::internal::UploadChunkRequest req( "projects/_/buckets/test-bucket/blah/blah", 0, {}, @@ -222,6 +236,20 @@ TEST_F(GrpcConfigureClientContext, ApplyRoutingHeadersAppendObject) { "bucket=projects%2F_%2Fbuckets%2Ftest-bucket"))); } +TEST_F(GrpcConfigureClientContext, + ApplyRoutingHeadersAppendObjectWithRoutingToken) { + auto spec = google::storage::v2::AppendObjectSpec{}; + spec.set_bucket("projects/_/buckets/test-bucket"); + + grpc::ClientContext context; + ApplyRoutingHeaders(context, spec, {"test-token"}); + auto metadata = GetMetadata(context); + EXPECT_THAT(metadata, + Contains(Pair("x-goog-request-params", + "bucket=projects%2F_%2Fbuckets%2Ftest-bucket&" + "routing_token=test-token"))); +} + } // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage_internal