diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml
new file mode 100644
index 00000000000..8a56894c12d
--- /dev/null
+++ b/.github/workflows/unmanaged_dependency_check.yaml
@@ -0,0 +1,22 @@
+on:
+ pull_request:
+name: Unmanaged dependency check
+jobs:
+ unmanaged_dependency_check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-java@v3
+ with:
+ distribution: temurin
+ java-version: 11
+ - name: Install modules
+ shell: bash
+ run: |
+ # No argument to build.sh installs the modules in local Maven
+ # repository
+ .kokoro/build.sh
+ - name: Unmanaged dependency check
+ uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@unmanaged-dependencies-check-latest
+ with:
+ bom-path: google-cloud-spanner-bom/pom.xml
diff --git a/.gitignore b/.gitignore
index ccf024c145b..0141aa5d4c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ nosetests.xml
.settings
.DS_Store
.classpath
+.tool-versions
# Built documentation
docs/
diff --git a/.kokoro/release/bump_snapshot.cfg b/.kokoro/release/bump_snapshot.cfg
deleted file mode 100644
index 6b870903f64..00000000000
--- a/.kokoro/release/bump_snapshot.cfg
+++ /dev/null
@@ -1,53 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-# Build logs will be here
-action {
- define_artifacts {
- regex: "**/*sponge_log.xml"
- }
-}
-
-# Download trampoline resources.
-gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
-
-# Use the trampoline script to run in docker.
-build_file: "java-spanner/.kokoro/trampoline.sh"
-
-# Configure the docker image for kokoro-trampoline.
-env_vars: {
- key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-kokoro-resources/node:10-user"
-}
-
-env_vars: {
- key: "TRAMPOLINE_BUILD_FILE"
- value: "github/java-spanner/.kokoro/release/bump_snapshot.sh"
-}
-
-# tokens used by release-please to keep an up-to-date release PR.
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "github-magic-proxy-key-release-please"
- }
- }
-}
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "github-magic-proxy-token-release-please"
- }
- }
-}
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "github-magic-proxy-url-release-please"
- }
- }
-}
diff --git a/.kokoro/release/bump_snapshot.sh b/.kokoro/release/bump_snapshot.sh
deleted file mode 100755
index 65dc2b5a412..00000000000
--- a/.kokoro/release/bump_snapshot.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-
-# Copyright 2019 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.
-
-set -eo pipefail
-
-export NPM_CONFIG_PREFIX=/home/node/.npm-global
-
-if [ -f ${KOKORO_KEYSTORE_DIR}/73713_github-magic-proxy-url-release-please ]; then
- # Groom the snapshot release PR immediately after publishing a release
- npx release-please release-pr --token=${KOKORO_KEYSTORE_DIR}/73713_github-magic-proxy-token-release-please \
- --repo-url=googleapis/java-spanner \
- --package-name="spanner" \
- --api-url=${KOKORO_KEYSTORE_DIR}/73713_github-magic-proxy-url-release-please \
- --proxy-key=${KOKORO_KEYSTORE_DIR}/73713_github-magic-proxy-key-release-please \
- --snapshot \
- --release-type=java-auth-yoshi
-fi
diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg
deleted file mode 100644
index 7872aea54d8..00000000000
--- a/.kokoro/release/common.cfg
+++ /dev/null
@@ -1,49 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-# Download trampoline resources.
-gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
-
-# Use the trampoline script to run in docker.
-build_file: "java-spanner/.kokoro/trampoline.sh"
-
-# Configure the docker image for kokoro-trampoline.
-env_vars: {
- key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-kokoro-resources/java11"
-}
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 70247
- keyname: "maven-gpg-keyring"
- }
- }
-}
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 70247
- keyname: "maven-gpg-passphrase"
- }
- }
-}
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 70247
- keyname: "maven-gpg-pubkeyring"
- }
- }
-}
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 70247
- keyname: "sonatype-credentials"
- }
- }
-}
diff --git a/.kokoro/release/common.sh b/.kokoro/release/common.sh
deleted file mode 100755
index 7f78ee414fa..00000000000
--- a/.kokoro/release/common.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/bash
-# Copyright 2018 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
-#
-# http://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.
-
-set -eo pipefail
-
-# Get secrets from keystore and set and environment variables
-setup_environment_secrets() {
- export GPG_PASSPHRASE=$(cat ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-passphrase)
- export GPG_TTY=$(tty)
- export GPG_HOMEDIR=/gpg
- mkdir $GPG_HOMEDIR
- mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-pubkeyring $GPG_HOMEDIR/pubring.gpg
- mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-keyring $GPG_HOMEDIR/secring.gpg
- export SONATYPE_USERNAME=$(cat ${KOKORO_KEYSTORE_DIR}/70247_sonatype-credentials | cut -f1 -d'|')
- export SONATYPE_PASSWORD=$(cat ${KOKORO_KEYSTORE_DIR}/70247_sonatype-credentials | cut -f2 -d'|')
-}
-
-create_settings_xml_file() {
- echo "
-
-
- ossrh
- ${SONATYPE_USERNAME}
- ${SONATYPE_PASSWORD}
-
-
- sonatype-nexus-staging
- ${SONATYPE_USERNAME}
- ${SONATYPE_PASSWORD}
-
-
- sonatype-nexus-snapshots
- ${SONATYPE_USERNAME}
- ${SONATYPE_PASSWORD}
-
-
-" > $1
-}
\ No newline at end of file
diff --git a/.kokoro/release/drop.cfg b/.kokoro/release/drop.cfg
deleted file mode 100644
index d541e5ac427..00000000000
--- a/.kokoro/release/drop.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-env_vars: {
- key: "TRAMPOLINE_BUILD_FILE"
- value: "github/java-spanner/.kokoro/release/drop.sh"
-}
diff --git a/.kokoro/release/drop.sh b/.kokoro/release/drop.sh
deleted file mode 100755
index 742ec1a8860..00000000000
--- a/.kokoro/release/drop.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-# Copyright 2018 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
-#
-# http://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.
-
-set -eo pipefail
-
-# STAGING_REPOSITORY_ID must be set
-if [ -z "${STAGING_REPOSITORY_ID}" ]; then
- echo "Missing STAGING_REPOSITORY_ID environment variable"
- exit 1
-fi
-
-source $(dirname "$0")/common.sh
-pushd $(dirname "$0")/../../
-
-setup_environment_secrets
-create_settings_xml_file "settings.xml"
-
-mvn nexus-staging:drop -B \
- --settings=settings.xml \
- -DstagingRepositoryId=${STAGING_REPOSITORY_ID}
diff --git a/.kokoro/release/promote.cfg b/.kokoro/release/promote.cfg
deleted file mode 100644
index 5551fdd0dd7..00000000000
--- a/.kokoro/release/promote.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-env_vars: {
- key: "TRAMPOLINE_BUILD_FILE"
- value: "github/java-spanner/.kokoro/release/promote.sh"
-}
diff --git a/.kokoro/release/promote.sh b/.kokoro/release/promote.sh
deleted file mode 100755
index 3cac3d8a97d..00000000000
--- a/.kokoro/release/promote.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-# Copyright 2018 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
-#
-# http://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.
-
-set -eo pipefail
-
-# STAGING_REPOSITORY_ID must be set
-if [ -z "${STAGING_REPOSITORY_ID}" ]; then
- echo "Missing STAGING_REPOSITORY_ID environment variable"
- exit 1
-fi
-
-source $(dirname "$0")/common.sh
-
-pushd $(dirname "$0")/../../
-
-setup_environment_secrets
-create_settings_xml_file "settings.xml"
-
-mvn nexus-staging:release -B \
- -DperformRelease=true \
- --settings=settings.xml \
- -DstagingRepositoryId=${STAGING_REPOSITORY_ID}
diff --git a/.kokoro/release/publish_javadoc.cfg b/.kokoro/release/publish_javadoc.cfg
deleted file mode 100644
index 59b87a36b2b..00000000000
--- a/.kokoro/release/publish_javadoc.cfg
+++ /dev/null
@@ -1,23 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/doc-templates/"
-
-env_vars: {
- key: "STAGING_BUCKET"
- value: "docs-staging"
-}
-
-env_vars: {
- key: "TRAMPOLINE_BUILD_FILE"
- value: "github/java-spanner/.kokoro/release/publish_javadoc.sh"
-}
-
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "docuploader_service_account"
- }
- }
-}
diff --git a/.kokoro/release/publish_javadoc.sh b/.kokoro/release/publish_javadoc.sh
deleted file mode 100755
index 2a5ac207734..00000000000
--- a/.kokoro/release/publish_javadoc.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/bash
-# Copyright 2019 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
-#
-# http://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.
-
-set -eo pipefail
-
-if [[ -z "${CREDENTIALS}" ]]; then
- CREDENTIALS=${KOKORO_KEYSTORE_DIR}/73713_docuploader_service_account
-fi
-
-if [[ -z "${STAGING_BUCKET}" ]]; then
- echo "Need to set STAGING_BUCKET environment variable"
- exit 1
-fi
-
-# work from the git root directory
-pushd $(dirname "$0")/../../
-
-# install docuploader package
-python3 -m pip install --require-hashes -r .kokoro/requirements.txt
-
-# compile all packages
-mvn clean install -B -q -DskipTests=true
-
-export NAME=google-cloud-spanner
-export VERSION=$(grep ${NAME}: versions.txt | cut -d: -f3)
-
-# build the docs
-mvn site -B -q
-
-pushd target/site/apidocs
-
-# create metadata
-python3 -m docuploader create-metadata \
- --name ${NAME} \
- --version ${VERSION} \
- --language java
-
-# upload docs
-python3 -m docuploader upload . \
- --credentials ${CREDENTIALS} \
- --staging-bucket ${STAGING_BUCKET}
diff --git a/.kokoro/release/publish_javadoc11.cfg b/.kokoro/release/publish_javadoc11.cfg
deleted file mode 100644
index a432da71657..00000000000
--- a/.kokoro/release/publish_javadoc11.cfg
+++ /dev/null
@@ -1,30 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-# cloud-rad production
-env_vars: {
- key: "STAGING_BUCKET_V2"
- value: "docs-staging-v2"
-}
-
-# Configure the docker image for kokoro-trampoline
-env_vars: {
- key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-kokoro-resources/java11"
-}
-
-env_vars: {
- key: "TRAMPOLINE_BUILD_FILE"
- value: "github/java-spanner/.kokoro/release/publish_javadoc11.sh"
-}
-
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "docuploader_service_account"
- }
- }
-}
-
-# Downloads docfx doclet resource. This will be in ${KOKORO_GFILE_DIR}/
-gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/docfx"
diff --git a/.kokoro/release/publish_javadoc11.sh b/.kokoro/release/publish_javadoc11.sh
deleted file mode 100755
index fa58188fb4e..00000000000
--- a/.kokoro/release/publish_javadoc11.sh
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/bin/bash
-# Copyright 2021 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
-#
-# http://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.
-
-set -eo pipefail
-
-if [[ -z "${CREDENTIALS}" ]]; then
- CREDENTIALS=${KOKORO_KEYSTORE_DIR}/73713_docuploader_service_account
-fi
-
-if [[ -z "${STAGING_BUCKET_V2}" ]]; then
- echo "Need to set STAGING_BUCKET_V2 environment variable"
- exit 1
-fi
-
-# work from the git root directory
-pushd $(dirname "$0")/../../
-
-# install docuploader package
-python3 -m pip install --require-hashes -r .kokoro/requirements.txt
-
-# compile all packages
-mvn clean install -B -q -DskipTests=true
-
-export NAME=google-cloud-spanner
-export VERSION=$(grep ${NAME}: versions.txt | cut -d: -f3)
-
-# cloud RAD generation
-mvn clean javadoc:aggregate -B -q -P docFX
-# include CHANGELOG
-cp CHANGELOG.md target/docfx-yml/history.md
-
-pushd target/docfx-yml
-
-# create metadata
-python3 -m docuploader create-metadata \
- --name ${NAME} \
- --version ${VERSION} \
- --xrefs devsite://java/gax \
- --xrefs devsite://java/google-cloud-core \
- --xrefs devsite://java/api-common \
- --xrefs devsite://java/proto-google-common-protos \
- --xrefs devsite://java/google-api-client \
- --xrefs devsite://java/google-http-client \
- --xrefs devsite://java/protobuf \
- --language java
-
-# upload yml to production bucket
-python3 -m docuploader upload . \
- --credentials ${CREDENTIALS} \
- --staging-bucket ${STAGING_BUCKET_V2} \
- --destination-prefix docfx
diff --git a/.kokoro/release/snapshot.cfg b/.kokoro/release/snapshot.cfg
deleted file mode 100644
index 1f10492392c..00000000000
--- a/.kokoro/release/snapshot.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-env_vars: {
- key: "TRAMPOLINE_BUILD_FILE"
- value: "github/java-spanner/.kokoro/release/snapshot.sh"
-}
\ No newline at end of file
diff --git a/.kokoro/release/snapshot.sh b/.kokoro/release/snapshot.sh
deleted file mode 100755
index 1f55b77024d..00000000000
--- a/.kokoro/release/snapshot.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-# Copyright 2019 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
-#
-# http://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.
-
-set -eo pipefail
-
-source $(dirname "$0")/common.sh
-MAVEN_SETTINGS_FILE=$(realpath $(dirname "$0")/../../)/settings.xml
-pushd $(dirname "$0")/../../
-
-# ensure we're trying to push a snapshot (no-result returns non-zero exit code)
-grep SNAPSHOT versions.txt
-
-setup_environment_secrets
-create_settings_xml_file "settings.xml"
-
-mvn clean deploy -B \
- --settings ${MAVEN_SETTINGS_FILE} \
- -DperformRelease=true \
- -Dgpg.executable=gpg \
- -Dgpg.passphrase=${GPG_PASSPHRASE} \
- -Dgpg.homedir=${GPG_HOMEDIR}
diff --git a/.kokoro/release/stage.cfg b/.kokoro/release/stage.cfg
deleted file mode 100644
index 462d561d2d3..00000000000
--- a/.kokoro/release/stage.cfg
+++ /dev/null
@@ -1,19 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-env_vars: {
- key: "TRAMPOLINE_BUILD_FILE"
- value: "github/java-spanner/.kokoro/release/stage.sh"
-}
-
-# Need to save the properties file
-action {
- define_artifacts {
- regex: "github/java-spanner/target/nexus-staging/staging/*.properties"
- strip_prefix: "github/java-spanner"
- }
-}
-
-env_vars: {
- key: "SECRET_MANAGER_KEYS"
- value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem"
-}
diff --git a/.kokoro/release/stage.sh b/.kokoro/release/stage.sh
deleted file mode 100755
index 61e714d6ba5..00000000000
--- a/.kokoro/release/stage.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/bin/bash
-# Copyright 2018 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
-#
-# http://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.
-
-set -eo pipefail
-
-# Start the releasetool reporter
-requirementsFile=$(realpath $(dirname "${BASH_SOURCE[0]}")/../requirements.txt)
-python3 -m pip install --require-hashes -r $requirementsFile
-python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script
-
-source $(dirname "$0")/common.sh
-source $(dirname "$0")/../common.sh
-MAVEN_SETTINGS_FILE=$(realpath $(dirname "$0")/../../)/settings.xml
-pushd $(dirname "$0")/../../
-
-setup_environment_secrets
-create_settings_xml_file "settings.xml"
-
-# attempt to stage 3 times with exponential backoff (starting with 10 seconds)
-retry_with_backoff 3 10 \
- mvn clean deploy -B \
- --settings ${MAVEN_SETTINGS_FILE} \
- -DskipTests=true \
- -Dclirr.skip=true \
- -DperformRelease=true \
- -Dgpg.executable=gpg \
- -Dgpg.passphrase=${GPG_PASSPHRASE} \
- -Dgpg.homedir=${GPG_HOMEDIR}
-
-if [[ -n "${AUTORELEASE_PR}" ]]
-then
- mvn nexus-staging:release -B \
- -DperformRelease=true \
- --settings=settings.xml
-fi
diff --git a/.kokoro/requirements.in b/.kokoro/requirements.in
deleted file mode 100644
index 2092cc741d9..00000000000
--- a/.kokoro/requirements.in
+++ /dev/null
@@ -1,6 +0,0 @@
-gcp-docuploader
-gcp-releasetool>=1.10.5 # required for compatibility with cryptography>=39.x
-wheel
-setuptools
-typing-extensions
-click<8.1.0
\ No newline at end of file
diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt
deleted file mode 100644
index 445c5c1f092..00000000000
--- a/.kokoro/requirements.txt
+++ /dev/null
@@ -1,491 +0,0 @@
-#
-# This file is autogenerated by pip-compile with Python 3.9
-# by the following command:
-#
-# pip-compile requirements.in --generate-hashes --upgrade
-#
-attrs==23.1.0 \
- --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
- --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
- # via gcp-releasetool
-cachetools==5.3.1 \
- --hash=sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590 \
- --hash=sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b
- # via google-auth
-certifi==2023.7.22 \
- --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \
- --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9
- # via requests
-cffi==1.15.1 \
- --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \
- --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \
- --hash=sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104 \
- --hash=sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426 \
- --hash=sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405 \
- --hash=sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375 \
- --hash=sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a \
- --hash=sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e \
- --hash=sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc \
- --hash=sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf \
- --hash=sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185 \
- --hash=sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497 \
- --hash=sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3 \
- --hash=sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35 \
- --hash=sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c \
- --hash=sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83 \
- --hash=sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21 \
- --hash=sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca \
- --hash=sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984 \
- --hash=sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac \
- --hash=sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd \
- --hash=sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee \
- --hash=sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a \
- --hash=sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2 \
- --hash=sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192 \
- --hash=sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7 \
- --hash=sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585 \
- --hash=sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f \
- --hash=sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e \
- --hash=sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27 \
- --hash=sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b \
- --hash=sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e \
- --hash=sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e \
- --hash=sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d \
- --hash=sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c \
- --hash=sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415 \
- --hash=sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82 \
- --hash=sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02 \
- --hash=sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314 \
- --hash=sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325 \
- --hash=sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c \
- --hash=sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3 \
- --hash=sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914 \
- --hash=sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045 \
- --hash=sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d \
- --hash=sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9 \
- --hash=sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5 \
- --hash=sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2 \
- --hash=sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c \
- --hash=sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3 \
- --hash=sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2 \
- --hash=sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8 \
- --hash=sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d \
- --hash=sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d \
- --hash=sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9 \
- --hash=sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162 \
- --hash=sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76 \
- --hash=sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4 \
- --hash=sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e \
- --hash=sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9 \
- --hash=sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6 \
- --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \
- --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \
- --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0
- # via cryptography
-charset-normalizer==3.2.0 \
- --hash=sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96 \
- --hash=sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c \
- --hash=sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710 \
- --hash=sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706 \
- --hash=sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020 \
- --hash=sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252 \
- --hash=sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad \
- --hash=sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329 \
- --hash=sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a \
- --hash=sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f \
- --hash=sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6 \
- --hash=sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4 \
- --hash=sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a \
- --hash=sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46 \
- --hash=sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2 \
- --hash=sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23 \
- --hash=sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace \
- --hash=sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd \
- --hash=sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982 \
- --hash=sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10 \
- --hash=sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2 \
- --hash=sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea \
- --hash=sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09 \
- --hash=sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5 \
- --hash=sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149 \
- --hash=sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489 \
- --hash=sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9 \
- --hash=sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80 \
- --hash=sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592 \
- --hash=sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3 \
- --hash=sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6 \
- --hash=sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed \
- --hash=sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c \
- --hash=sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200 \
- --hash=sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a \
- --hash=sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e \
- --hash=sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d \
- --hash=sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6 \
- --hash=sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623 \
- --hash=sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669 \
- --hash=sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3 \
- --hash=sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa \
- --hash=sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9 \
- --hash=sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2 \
- --hash=sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f \
- --hash=sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1 \
- --hash=sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4 \
- --hash=sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a \
- --hash=sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8 \
- --hash=sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3 \
- --hash=sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029 \
- --hash=sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f \
- --hash=sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959 \
- --hash=sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22 \
- --hash=sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7 \
- --hash=sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952 \
- --hash=sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346 \
- --hash=sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e \
- --hash=sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d \
- --hash=sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299 \
- --hash=sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd \
- --hash=sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a \
- --hash=sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3 \
- --hash=sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037 \
- --hash=sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94 \
- --hash=sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c \
- --hash=sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858 \
- --hash=sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a \
- --hash=sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449 \
- --hash=sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c \
- --hash=sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918 \
- --hash=sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1 \
- --hash=sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c \
- --hash=sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac \
- --hash=sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa
- # via requests
-click==8.0.4 \
- --hash=sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1 \
- --hash=sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb
- # via
- # -r requirements.in
- # gcp-docuploader
- # gcp-releasetool
-colorlog==6.7.0 \
- --hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \
- --hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5
- # via gcp-docuploader
-cryptography==41.0.6 \
- --hash=sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596 \
- --hash=sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c \
- --hash=sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660 \
- --hash=sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4 \
- --hash=sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead \
- --hash=sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed \
- --hash=sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3 \
- --hash=sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7 \
- --hash=sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09 \
- --hash=sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c \
- --hash=sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43 \
- --hash=sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65 \
- --hash=sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6 \
- --hash=sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da \
- --hash=sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c \
- --hash=sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b \
- --hash=sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8 \
- --hash=sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c \
- --hash=sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d \
- --hash=sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9 \
- --hash=sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86 \
- --hash=sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36 \
- --hash=sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae
- # via
- # gcp-releasetool
- # secretstorage
-gcp-docuploader==0.6.5 \
- --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \
- --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea
- # via -r requirements.in
-gcp-releasetool==1.16.0 \
- --hash=sha256:27bf19d2e87aaa884096ff941aa3c592c482be3d6a2bfe6f06afafa6af2353e3 \
- --hash=sha256:a316b197a543fd036209d0caba7a8eb4d236d8e65381c80cbc6d7efaa7606d63
- # via -r requirements.in
-google-api-core==2.11.1 \
- --hash=sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a \
- --hash=sha256:d92a5a92dc36dd4f4b9ee4e55528a90e432b059f93aee6ad857f9de8cc7ae94a
- # via
- # google-cloud-core
- # google-cloud-storage
-google-auth==2.22.0 \
- --hash=sha256:164cba9af4e6e4e40c3a4f90a1a6c12ee56f14c0b4868d1ca91b32826ab334ce \
- --hash=sha256:d61d1b40897407b574da67da1a833bdc10d5a11642566e506565d1b1a46ba873
- # via
- # gcp-releasetool
- # google-api-core
- # google-cloud-core
- # google-cloud-storage
-google-cloud-core==2.3.3 \
- --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \
- --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863
- # via google-cloud-storage
-google-cloud-storage==2.10.0 \
- --hash=sha256:934b31ead5f3994e5360f9ff5750982c5b6b11604dc072bc452c25965e076dc7 \
- --hash=sha256:9433cf28801671de1c80434238fb1e7e4a1ba3087470e90f70c928ea77c2b9d7
- # via gcp-docuploader
-google-crc32c==1.5.0 \
- --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \
- --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \
- --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \
- --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \
- --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \
- --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \
- --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \
- --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \
- --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \
- --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \
- --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \
- --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \
- --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \
- --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \
- --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \
- --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \
- --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \
- --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \
- --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \
- --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \
- --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \
- --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \
- --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \
- --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \
- --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \
- --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \
- --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \
- --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \
- --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \
- --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \
- --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \
- --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \
- --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \
- --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \
- --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \
- --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \
- --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \
- --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \
- --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \
- --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \
- --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \
- --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \
- --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \
- --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \
- --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \
- --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \
- --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \
- --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \
- --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \
- --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \
- --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \
- --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \
- --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \
- --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \
- --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \
- --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \
- --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \
- --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \
- --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \
- --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \
- --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \
- --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \
- --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \
- --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \
- --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \
- --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \
- --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \
- --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4
- # via google-resumable-media
-google-resumable-media==2.5.0 \
- --hash=sha256:218931e8e2b2a73a58eb354a288e03a0fd5fb1c4583261ac6e4c078666468c93 \
- --hash=sha256:da1bd943e2e114a56d85d6848497ebf9be6a14d3db23e9fc57581e7c3e8170ec
- # via google-cloud-storage
-googleapis-common-protos==1.59.1 \
- --hash=sha256:0cbedb6fb68f1c07e18eb4c48256320777707e7d0c55063ae56c15db3224a61e \
- --hash=sha256:b35d530fe825fb4227857bc47ad84c33c809ac96f312e13182bdeaa2abe1178a
- # via google-api-core
-idna==3.4 \
- --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
- --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
- # via requests
-importlib-metadata==6.8.0 \
- --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \
- --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743
- # via keyring
-jaraco-classes==3.3.0 \
- --hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \
- --hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621
- # via keyring
-jeepney==0.8.0 \
- --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \
- --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755
- # via
- # keyring
- # secretstorage
-jinja2==3.1.2 \
- --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
- --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
- # via gcp-releasetool
-keyring==24.2.0 \
- --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \
- --hash=sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509
- # via gcp-releasetool
-markupsafe==2.1.3 \
- --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
- --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
- --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
- --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
- --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
- --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
- --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
- --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
- --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
- --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
- --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
- --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
- --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
- --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
- --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
- --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
- --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
- --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
- --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
- --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
- --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
- --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
- --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
- --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
- --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
- --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
- --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
- --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
- --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
- --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
- --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
- --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
- --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
- --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
- --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
- --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
- --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
- --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
- --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
- --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
- --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
- --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
- --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
- --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
- --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
- --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
- --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
- --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
- --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
- --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2
- # via jinja2
-more-itertools==9.1.0 \
- --hash=sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d \
- --hash=sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3
- # via jaraco-classes
-packaging==23.1 \
- --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \
- --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f
- # via gcp-releasetool
-protobuf==3.20.3 \
- --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \
- --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \
- --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \
- --hash=sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b \
- --hash=sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050 \
- --hash=sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9 \
- --hash=sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7 \
- --hash=sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454 \
- --hash=sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480 \
- --hash=sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469 \
- --hash=sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c \
- --hash=sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e \
- --hash=sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db \
- --hash=sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905 \
- --hash=sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b \
- --hash=sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86 \
- --hash=sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4 \
- --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \
- --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \
- --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \
- --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \
- --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee
- # via
- # gcp-docuploader
- # gcp-releasetool
- # google-api-core
- # googleapis-common-protos
-pyasn1==0.5.0 \
- --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \
- --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde
- # via
- # pyasn1-modules
- # rsa
-pyasn1-modules==0.3.0 \
- --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \
- --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d
- # via google-auth
-pycparser==2.21 \
- --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
- --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
- # via cffi
-pyjwt==2.7.0 \
- --hash=sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1 \
- --hash=sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074
- # via gcp-releasetool
-pyperclip==1.8.2 \
- --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57
- # via gcp-releasetool
-python-dateutil==2.8.2 \
- --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
- --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
- # via gcp-releasetool
-requests==2.31.0 \
- --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
- --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
- # via
- # gcp-releasetool
- # google-api-core
- # google-cloud-storage
-rsa==4.9 \
- --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \
- --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21
- # via google-auth
-secretstorage==3.3.3 \
- --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \
- --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99
- # via keyring
-six==1.16.0 \
- --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
- --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
- # via
- # gcp-docuploader
- # google-auth
- # python-dateutil
-typing-extensions==4.7.1 \
- --hash=sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36 \
- --hash=sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2
- # via -r requirements.in
-urllib3==1.26.18 \
- --hash=sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07 \
- --hash=sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0
- # via
- # google-auth
- # requests
-wheel==0.40.0 \
- --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \
- --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247
- # via -r requirements.in
-zipp==3.16.1 \
- --hash=sha256:0b37c326d826d5ca35f2b9685cd750292740774ef16190008b00a0227c256fe0 \
- --hash=sha256:857b158da2cbf427b376da1c24fd11faecbac5a4ac7523c3607f8a01f94c2ec0
- # via importlib-metadata
-
-# WARNING: The following packages were not pinned, but pip requires them to be
-# pinned when the requirements file includes hashes and the requirement is not
-# satisfied by a package already installed. Consider using the --allow-unsafe flag.
-# setuptools
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0d281d6f31..e8680d3ccfe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
# Changelog
+## [6.58.0](https://github.com/googleapis/java-spanner/compare/v6.57.0...v6.58.0) (2024-02-08)
+
+
+### Features
+
+* Open telemetry implementation ([#2770](https://github.com/googleapis/java-spanner/issues/2770)) ([244d6a8](https://github.com/googleapis/java-spanner/commit/244d6a836795bf07dacd6b766436dbd6bf5fa912))
+* **spanner:** Support max_commit_delay in Spanner transactions ([#2854](https://github.com/googleapis/java-spanner/issues/2854)) ([e2b7ae6](https://github.com/googleapis/java-spanner/commit/e2b7ae66648ea775c18c71ab353edd6c0f50e7ac))
+* Support Directed Read in Connection API ([#2855](https://github.com/googleapis/java-spanner/issues/2855)) ([ee477c2](https://github.com/googleapis/java-spanner/commit/ee477c2e7c509ce4b7c43da3b68c1433c59e46fb))
+
+
+### Bug Fixes
+
+* Cast for Proto type ([#2862](https://github.com/googleapis/java-spanner/issues/2862)) ([0a95dba](https://github.com/googleapis/java-spanner/commit/0a95dba47681c9c4cc4e41ecfb5dadec6357bff6))
+* Ignore UnsupportedOperationException for virtual threads ([#2866](https://github.com/googleapis/java-spanner/issues/2866)) ([aa9ad7f](https://github.com/googleapis/java-spanner/commit/aa9ad7f5a5e2405e8082a542916c3d1fa7d0fa25))
+* Use default query options with statement cache ([#2860](https://github.com/googleapis/java-spanner/issues/2860)) ([741e4cf](https://github.com/googleapis/java-spanner/commit/741e4cf4eb51c4635078cfe2c52b7462bd4cbbd8))
+
+
+### Dependencies
+
+* Update dependency com.google.cloud:google-cloud-shared-dependencies to v3.24.0 ([#2856](https://github.com/googleapis/java-spanner/issues/2856)) ([968877e](https://github.com/googleapis/java-spanner/commit/968877e4eff7da3ff27180c2a6129b04922d1af4))
+
## [6.57.0](https://github.com/googleapis/java-spanner/compare/v6.56.0...v6.57.0) (2024-01-29)
diff --git a/README.md b/README.md
index 4784fc5caeb..288afeef263 100644
--- a/README.md
+++ b/README.md
@@ -50,20 +50,20 @@ If you are using Maven without the BOM, add this to your dependencies:
If you are using Gradle 5.x or later, add this to your dependencies:
```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.30.0')
+implementation platform('com.google.cloud:libraries-bom:26.31.0')
implementation 'com.google.cloud:google-cloud-spanner'
```
If you are using Gradle without BOM, add this to your dependencies:
```Groovy
-implementation 'com.google.cloud:google-cloud-spanner:6.56.0'
+implementation 'com.google.cloud:google-cloud-spanner:6.57.0'
```
If you are using SBT, add this to your dependencies:
```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.56.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.57.0"
```
@@ -444,7 +444,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
[kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html
[stability-image]: https://img.shields.io/badge/stability-stable-green
[maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg
-[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.56.0
+[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.57.0
[authentication]: https://github.com/googleapis/google-cloud-java#authentication
[auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
[predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles
diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml
index eb873766fa0..2166bc5c6a0 100644
--- a/google-cloud-spanner-bom/pom.xml
+++ b/google-cloud-spanner-bom/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.cloud
google-cloud-spanner-bom
- 6.57.0
+ 6.58.0
pom
com.google.cloud
@@ -53,43 +53,43 @@
com.google.cloud
google-cloud-spanner
- 6.57.0
+ 6.58.0
com.google.cloud
google-cloud-spanner
test-jar
- 6.57.0
+ 6.58.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.57.0
+ 6.58.0
diff --git a/google-cloud-spanner-executor/pom.xml b/google-cloud-spanner-executor/pom.xml
index b7cbf4daf65..b0dda360aac 100644
--- a/google-cloud-spanner-executor/pom.xml
+++ b/google-cloud-spanner-executor/pom.xml
@@ -5,14 +5,14 @@
4.0.0
com.google.cloud
google-cloud-spanner-executor
- 6.57.0
+ 6.58.0
jar
Google Cloud Spanner Executor
com.google.cloud
google-cloud-spanner-parent
- 6.57.0
+ 6.58.0
diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml
index 56e946eb3d4..54eae11d51b 100644
--- a/google-cloud-spanner/clirr-ignored-differences.xml
+++ b/google-cloud-spanner/clirr-ignored-differences.xml
@@ -426,7 +426,6 @@
com/google/cloud/spanner/connection/Connection
void rollbackToSavepoint(java.lang.String)
-
7012
@@ -540,4 +539,23 @@
com/google/cloud/spanner/Dialect
java.lang.String getDefaultSchema()
+
+ 7005
+ com/google/cloud/spanner/PartitionedDmlTransaction
+ void setSpan(io.opencensus.trace.Span)
+ void setSpan(com.google.cloud.spanner.ISpan)
+
+
+
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ com.google.spanner.v1.DirectedReadOptions getDirectedRead()
+
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ void setDirectedRead(com.google.spanner.v1.DirectedReadOptions)
+
+
diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml
index 860a10fc46a..5792068ef23 100644
--- a/google-cloud-spanner/pom.xml
+++ b/google-cloud-spanner/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.cloud
google-cloud-spanner
- 6.57.0
+ 6.58.0
jar
Google Cloud Spanner
https://github.com/googleapis/java-spanner
@@ -11,7 +11,7 @@
com.google.cloud
google-cloud-spanner-parent
- 6.57.0
+ 6.58.0
google-cloud-spanner
@@ -247,6 +247,14 @@
opencensus-impl
test
+
+ io.opentelemetry
+ opentelemetry-api
+
+
+ io.opentelemetry
+ opentelemetry-context
+
com.google.auth
google-auth-library-oauth2-http
@@ -393,7 +401,6 @@
2.2
test
-
org.openjdk.jmh
@@ -407,9 +414,28 @@
1.37
test
-
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+ test
+
+
+ io.opentelemetry
+ opentelemetry-sdk-metrics
+ test
+
+
+ io.opentelemetry
+ opentelemetry-sdk-trace
+ test
+
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+ test
+
-
java9
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
index 0f4310f9b4d..0714b7651cc 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
@@ -39,6 +39,7 @@
import com.google.cloud.spanner.spi.v1.SpannerRpc;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.google.spanner.v1.BeginTransactionRequest;
@@ -53,8 +54,6 @@
import com.google.spanner.v1.Transaction;
import com.google.spanner.v1.TransactionOptions;
import com.google.spanner.v1.TransactionSelector;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Tracing;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
@@ -70,7 +69,8 @@ abstract class AbstractReadContext
abstract static class Builder, T extends AbstractReadContext> {
private SessionImpl session;
private SpannerRpc rpc;
- private Span span = Tracing.getTracer().getCurrentSpan();
+ private ISpan span;
+ private TraceWrapper tracer;
private int defaultPrefetchChunks = SpannerOptions.Builder.DEFAULT_PREFETCH_CHUNKS;
private QueryOptions defaultQueryOptions = SpannerOptions.Builder.DEFAULT_QUERY_OPTIONS;
private DirectedReadOptions defaultDirectedReadOption;
@@ -94,11 +94,16 @@ B setRpc(SpannerRpc rpc) {
return self();
}
- B setSpan(Span span) {
+ B setSpan(ISpan span) {
this.span = span;
return self();
}
+ B setTracer(TraceWrapper tracer) {
+ this.tracer = tracer;
+ return self();
+ }
+
B setDefaultPrefetchChunks(int defaultPrefetchChunks) {
this.defaultPrefetchChunks = defaultPrefetchChunks;
return self();
@@ -389,9 +394,12 @@ void initTransaction() {
}
transactionId = transaction.getId();
span.addAnnotation(
- "Transaction Creation Done", TraceUtil.getTransactionAnnotations(transaction));
+ "Transaction Creation Done",
+ ImmutableMap.of(
+ "Id", transaction.getId().toStringUtf8(), "Timestamp", timestamp.toString()));
+
} catch (SpannerException e) {
- span.addAnnotation("Transaction Creation Failed", TraceUtil.getExceptionAnnotations(e));
+ span.addAnnotation("Transaction Creation Failed", e);
throw e;
}
}
@@ -402,7 +410,8 @@ void initTransaction() {
final SessionImpl session;
final SpannerRpc rpc;
final ExecutorProvider executorProvider;
- Span span;
+ ISpan span;
+ TraceWrapper tracer;
private final int defaultPrefetchChunks;
private final QueryOptions defaultQueryOptions;
@@ -435,10 +444,11 @@ void initTransaction() {
this.span = builder.span;
this.executorProvider = builder.executorProvider;
this.clock = builder.clock;
+ this.tracer = builder.tracer;
}
@Override
- public void setSpan(Span span) {
+ public void setSpan(ISpan span) {
this.span = span;
}
@@ -692,6 +702,7 @@ ResultSet executeQueryInternalWithOptions(
MAX_BUFFERED_CHUNKS,
SpannerImpl.QUERY,
span,
+ tracer,
rpc.getExecuteQueryRetrySettings(),
rpc.getExecuteQueryRetryableCodes()) {
@Override
@@ -752,7 +763,7 @@ public final void invalidate() {
@Override
public void close() {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
synchronized (lock) {
isClosed = true;
}
@@ -837,6 +848,7 @@ ResultSet readInternalWithOptions(
MAX_BUFFERED_CHUNKS,
SpannerImpl.READ,
span,
+ tracer,
rpc.getReadRetrySettings(),
rpc.getReadRetryableCodes()) {
@Override
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
index c18e64165bc..4904a382f92 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractResultSet.java
@@ -37,7 +37,6 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.AbstractIterator;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.io.CharSource;
import com.google.common.util.concurrent.Uninterruptibles;
@@ -53,11 +52,6 @@
import com.google.spanner.v1.Transaction;
import com.google.spanner.v1.TypeCode;
import io.grpc.Context;
-import io.opencensus.common.Scope;
-import io.opencensus.trace.AttributeValue;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Tracer;
-import io.opencensus.trace.Tracing;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -87,7 +81,6 @@
/** Implementation of {@link ResultSet}. */
abstract class AbstractResultSet extends AbstractStructReader implements ResultSet {
- private static final Tracer tracer = Tracing.getTracer();
private static final com.google.protobuf.Value NULL_VALUE =
com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
@@ -485,7 +478,10 @@ private Object writeReplace() {
case PROTO:
builder
.set(fieldName)
- .to(Value.protoMessage((ByteArray) value, fieldType.getProtoTypeFqn()));
+ .to(
+ Value.protoMessage(
+ value == null ? null : ((LazyByteArray) value).getByteArray(),
+ fieldType.getProtoTypeFqn()));
break;
case ENUM:
builder.set(fieldName).to(Value.protoEnum((Long) value, fieldType.getProtoTypeFqn()));
@@ -817,7 +813,8 @@ protected Value getValueInternal(int columnIndex) {
case INT64:
return Value.int64(isNull ? null : getLongInternal(columnIndex));
case ENUM:
- return Value.protoEnum(getLongInternal(columnIndex), columnType.getProtoTypeFqn());
+ return Value.protoEnum(
+ isNull ? null : getLongInternal(columnIndex), columnType.getProtoTypeFqn());
case NUMERIC:
return Value.numeric(isNull ? null : getBigDecimalInternal(columnIndex));
case PG_NUMERIC:
@@ -833,7 +830,8 @@ protected Value getValueInternal(int columnIndex) {
case BYTES:
return Value.internalBytes(isNull ? null : getLazyBytesInternal(columnIndex));
case PROTO:
- return Value.protoMessage(getBytesInternal(columnIndex), columnType.getProtoTypeFqn());
+ return Value.protoMessage(
+ isNull ? null : getBytesInternal(columnIndex), columnType.getProtoTypeFqn());
case TIMESTAMP:
return Value.timestamp(isNull ? null : getTimestampInternal(columnIndex));
case DATE:
@@ -1206,7 +1204,8 @@ abstract static class ResumableStreamIterator extends AbstractIterator buffer = new LinkedList<>();
private final int maxBufferSize;
- private final Span span;
+ private final ISpan span;
+ private final TraceWrapper tracer;
private CloseableIterator stream;
private ByteString resumeToken;
private boolean finished;
@@ -1220,12 +1219,14 @@ abstract static class ResumableStreamIterator extends AbstractIterator retryableCodes) {
checkArgument(maxBufferSize >= 0);
this.maxBufferSize = maxBufferSize;
- this.span = tracer.spanBuilderWithExplicitParent(streamName, parent).startSpan();
+ this.tracer = tracer;
+ this.span = tracer.spanBuilderWithExplicitParent(streamName, parent);
this.streamingRetrySettings = Preconditions.checkNotNull(streamingRetrySettings);
this.retryableCodes = Preconditions.checkNotNull(retryableCodes);
this.backOff = newBackOff();
@@ -1281,11 +1282,7 @@ private static long nextBackOffMillis(BackOff backoff) throws SpannerException {
}
private void backoffSleep(Context context, long backoffMillis) throws SpannerException {
- tracer
- .getCurrentSpan()
- .addAnnotation(
- "Backing off",
- ImmutableMap.of("Delay", AttributeValue.longAttributeValue(backoffMillis)));
+ tracer.getCurrentSpan().addAnnotation("Backing off", "Delay", backoffMillis);
final CountDownLatch latch = new CountDownLatch(1);
final Context.CancellationListener listener =
ignored -> {
@@ -1325,7 +1322,7 @@ public void execute(Runnable command) {
public void close(@Nullable String message) {
if (stream != null) {
stream.close(message);
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
stream = null;
}
}
@@ -1343,11 +1340,9 @@ protected PartialResultSet computeNext() {
if (stream == null) {
span.addAnnotation(
"Starting/Resuming stream",
- ImmutableMap.of(
- "ResumeToken",
- AttributeValue.stringAttributeValue(
- resumeToken == null ? "null" : resumeToken.toStringUtf8())));
- try (Scope s = tracer.withSpan(span)) {
+ "ResumeToken",
+ resumeToken == null ? "null" : resumeToken.toStringUtf8());
+ try (IScope scope = tracer.withSpan(span)) {
// When start a new stream set the Span as current to make the gRPC Span a child of
// this Span.
stream = checkNotNull(startStream(resumeToken));
@@ -1387,9 +1382,7 @@ protected PartialResultSet computeNext() {
}
} catch (SpannerException spannerException) {
if (safeToRetry && isRetryable(spannerException)) {
- span.addAnnotation(
- "Stream broken. Safe to retry",
- TraceUtil.getExceptionAnnotations(spannerException));
+ span.addAnnotation("Stream broken. Safe to retry", spannerException);
logger.log(Level.FINE, "Retryable exception, will sleep and retry", spannerException);
// Truncate any items in the buffer before the last retry token.
while (!buffer.isEmpty() && buffer.getLast().getResumeToken().isEmpty()) {
@@ -1397,7 +1390,7 @@ protected PartialResultSet computeNext() {
}
assert buffer.isEmpty() || buffer.getLast().getResumeToken().equals(resumeToken);
stream = null;
- try (Scope s = tracer.withSpan(span)) {
+ try (IScope s = tracer.withSpan(span)) {
long delay = spannerException.getRetryDelayInMillis();
if (delay != -1) {
backoffSleep(context, delay);
@@ -1408,12 +1401,12 @@ protected PartialResultSet computeNext() {
continue;
}
- span.addAnnotation("Stream broken. Not safe to retry");
- TraceUtil.setWithFailure(span, spannerException);
+ span.addAnnotation("Stream broken. Not safe to retry", spannerException);
+ span.setStatus(spannerException);
throw spannerException;
} catch (RuntimeException e) {
- span.addAnnotation("Stream broken. Not safe to retry");
- TraceUtil.setWithFailure(span, e);
+ span.addAnnotation("Stream broken. Not safe to retry", e);
+ span.setStatus(e);
throw e;
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
index 53c49a5a54b..ef6f63d52ea 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java
@@ -340,14 +340,16 @@ public List getBooleanList(String columnName) {
@Override
public long[] getLongArray(int columnIndex) {
- checkNonNullOfType(columnIndex, Type.array(Type.int64()), columnIndex);
+ checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnIndex);
+ checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnIndex);
return getLongArrayInternal(columnIndex);
}
@Override
public long[] getLongArray(String columnName) {
int columnIndex = getColumnIndex(columnName);
- checkNonNullOfType(columnIndex, Type.array(Type.int64()), columnName);
+ checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnName);
+ checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnName);
return getLongArrayInternal(columnIndex);
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java
index 510bd02bc0e..8b20dd824a0 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java
@@ -28,31 +28,27 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Tracer;
-import io.opencensus.trace.Tracing;
/** Implementation of {@link AsyncTransactionManager}. */
final class AsyncTransactionManagerImpl
implements CommittableAsyncTransactionManager, SessionTransaction {
- private static final Tracer tracer = Tracing.getTracer();
private final SessionImpl session;
- private Span span;
+ private ISpan span;
private final Options options;
private TransactionRunnerImpl.TransactionContextImpl txn;
private TransactionState txnState;
private final SettableApiFuture commitResponse = SettableApiFuture.create();
- AsyncTransactionManagerImpl(SessionImpl session, Span span, TransactionOption... options) {
+ AsyncTransactionManagerImpl(SessionImpl session, ISpan span, TransactionOption... options) {
this.session = session;
this.span = span;
this.options = Options.fromTransactionOptions(options);
}
@Override
- public void setSpan(Span span) {
+ public void setSpan(ISpan span) {
this.span = span;
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
index eab90a266c9..664cde1edbb 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
@@ -30,7 +30,6 @@
import com.google.spanner.v1.PartitionReadRequest;
import com.google.spanner.v1.PartitionResponse;
import com.google.spanner.v1.TransactionSelector;
-import io.opencensus.trace.Tracing;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -62,7 +61,9 @@ public BatchReadOnlyTransaction batchReadOnlyTransaction(TimestampBound bound) {
.setExecutorProvider(sessionClient.getSpanner().getAsyncExecutorProvider())
.setDefaultPrefetchChunks(sessionClient.getSpanner().getDefaultPrefetchChunks())
.setDefaultDirectedReadOptions(
- sessionClient.getSpanner().getOptions().getDirectedReadOptions()),
+ sessionClient.getSpanner().getOptions().getDirectedReadOptions())
+ .setSpan(sessionClient.getSpanner().getTracer().getCurrentSpan())
+ .setTracer(sessionClient.getSpanner().getTracer()),
checkNotNull(bound));
}
@@ -81,7 +82,9 @@ public BatchReadOnlyTransaction batchReadOnlyTransaction(BatchTransactionId batc
.setExecutorProvider(sessionClient.getSpanner().getAsyncExecutorProvider())
.setDefaultPrefetchChunks(sessionClient.getSpanner().getDefaultPrefetchChunks())
.setDefaultDirectedReadOptions(
- sessionClient.getSpanner().getOptions().getDirectedReadOptions()),
+ sessionClient.getSpanner().getOptions().getDirectedReadOptions())
+ .setSpan(sessionClient.getSpanner().getTracer().getCurrentSpan())
+ .setTracer(sessionClient.getSpanner().getTracer()),
batchTransactionId);
}
@@ -95,7 +98,6 @@ private static class BatchReadOnlyTransactionImpl extends MultiUseReadOnlyTransa
super(builder.setTimestampBound(bound));
this.sessionName = session.getName();
this.options = session.getOptions();
- setSpan(Tracing.getTracer().getCurrentSpan());
initTransaction();
}
@@ -104,7 +106,6 @@ private static class BatchReadOnlyTransactionImpl extends MultiUseReadOnlyTransa
super(builder.setTransactionId(batchTransactionId.getTransactionId()));
this.sessionName = session.getName();
this.options = session.getOptions();
- setSpan(Tracing.getTracer().getCurrentSpan());
}
@Override
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
index 3835cb1f338..b63ad379305 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClientImpl.java
@@ -26,29 +26,25 @@
import com.google.common.base.Function;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.spanner.v1.BatchWriteResponse;
-import io.opencensus.common.Scope;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Tracer;
-import io.opencensus.trace.Tracing;
import javax.annotation.Nullable;
class DatabaseClientImpl implements DatabaseClient {
private static final String READ_WRITE_TRANSACTION = "CloudSpanner.ReadWriteTransaction";
private static final String READ_ONLY_TRANSACTION = "CloudSpanner.ReadOnlyTransaction";
private static final String PARTITION_DML_TRANSACTION = "CloudSpanner.PartitionDMLTransaction";
- private static final Tracer tracer = Tracing.getTracer();
-
+ private final TraceWrapper tracer;
@VisibleForTesting final String clientId;
@VisibleForTesting final SessionPool pool;
@VisibleForTesting
- DatabaseClientImpl(SessionPool pool) {
- this("", pool);
+ DatabaseClientImpl(SessionPool pool, TraceWrapper tracer) {
+ this("", pool, tracer);
}
- DatabaseClientImpl(String clientId, SessionPool pool) {
+ DatabaseClientImpl(String clientId, SessionPool pool, TraceWrapper tracer) {
this.clientId = clientId;
this.pool = pool;
+ this.tracer = tracer;
}
@VisibleForTesting
@@ -76,14 +72,14 @@ public Timestamp write(final Iterable mutations) throws SpannerExcepti
public CommitResponse writeWithOptions(
final Iterable mutations, final TransactionOption... options)
throws SpannerException {
- Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return runWithSessionRetry(session -> session.writeWithOptions(mutations, options));
} catch (RuntimeException e) {
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw e;
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
@@ -96,15 +92,15 @@ public Timestamp writeAtLeastOnce(final Iterable mutations) throws Spa
public CommitResponse writeAtLeastOnceWithOptions(
final Iterable mutations, final TransactionOption... options)
throws SpannerException {
- Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return runWithSessionRetry(
session -> session.writeAtLeastOnceWithOptions(mutations, options));
} catch (RuntimeException e) {
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw e;
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
@@ -112,136 +108,145 @@ public CommitResponse writeAtLeastOnceWithOptions(
public ServerStream batchWriteAtLeastOnce(
final Iterable mutationGroups, final TransactionOption... options)
throws SpannerException {
- Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return runWithSessionRetry(session -> session.batchWriteAtLeastOnce(mutationGroups, options));
} catch (RuntimeException e) {
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw e;
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
@Override
public ReadContext singleUse() {
- Span span = tracer.spanBuilder(READ_ONLY_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_ONLY_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().singleUse();
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public ReadContext singleUse(TimestampBound bound) {
- Span span = tracer.spanBuilder(READ_ONLY_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_ONLY_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().singleUse(bound);
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public ReadOnlyTransaction singleUseReadOnlyTransaction() {
- Span span = tracer.spanBuilder(READ_ONLY_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_ONLY_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().singleUseReadOnlyTransaction();
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public ReadOnlyTransaction singleUseReadOnlyTransaction(TimestampBound bound) {
- Span span = tracer.spanBuilder(READ_ONLY_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_ONLY_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().singleUseReadOnlyTransaction(bound);
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public ReadOnlyTransaction readOnlyTransaction() {
- Span span = tracer.spanBuilder(READ_ONLY_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_ONLY_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().readOnlyTransaction();
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public ReadOnlyTransaction readOnlyTransaction(TimestampBound bound) {
- Span span = tracer.spanBuilder(READ_ONLY_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_ONLY_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().readOnlyTransaction(bound);
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public TransactionRunner readWriteTransaction(TransactionOption... options) {
- Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().readWriteTransaction(options);
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
- } finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
}
}
@Override
public TransactionManager transactionManager(TransactionOption... options) {
- Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().transactionManager(options);
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public AsyncRunner runAsync(TransactionOption... options) {
- Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().runAsync(options);
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public AsyncTransactionManager transactionManagerAsync(TransactionOption... options) {
- Span span = tracer.spanBuilder(READ_WRITE_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(READ_WRITE_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return getSession().transactionManagerAsync(options);
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
@Override
public long executePartitionedUpdate(final Statement stmt, final UpdateOption... options) {
- Span span = tracer.spanBuilder(PARTITION_DML_TRANSACTION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(PARTITION_DML_TRANSACTION);
+ try (IScope s = tracer.withSpan(span)) {
return runWithSessionRetry(session -> session.executePartitionedUpdate(stmt, options));
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/IScope.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/IScope.java
new file mode 100644
index 00000000000..cbefe47b887
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/IScope.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner;
+
+/**
+ * This interface represents a scope that wraps both OpenCensus and OpenTelemetry scopes. It extends
+ * the AutoCloseable interface and overrides the close method that does not throw an exception.
+ */
+interface IScope extends AutoCloseable {
+ @Override
+ void close();
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ISpan.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ISpan.java
new file mode 100644
index 00000000000..ce837de0e58
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ISpan.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner;
+
+import java.util.Map;
+
+interface ISpan {
+
+ /**
+ * Adds an annotation to the OpenCensus and OpenTelemetry span.
+ *
+ * @param message the description of the annotation event.
+ * @param attributes the map of attribute key-value pairs that will be added; these are associated
+ * with this annotation.
+ */
+ void addAnnotation(String message, Map attributes);
+
+ void addAnnotation(String message);
+
+ void addAnnotation(String message, String key, String value);
+
+ void addAnnotation(String message, String key, long value);
+
+ void addAnnotation(String message, Throwable e);
+
+ void setStatus(Throwable e);
+
+ void setStatus(ErrorCode errorCode);
+
+ void end();
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MetricRegistryConstants.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MetricRegistryConstants.java
index 3512a75732d..d0098e961e6 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MetricRegistryConstants.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MetricRegistryConstants.java
@@ -65,13 +65,20 @@ class MetricRegistryConstants {
/** Unit to represent counts. */
static final String COUNT = "1";
+ static final String INSTRUMENTATION_SCOPE = "cloud.google.com/java";
+
+ static final String METRIC_PREFIX = "cloud.google.com/java/";
+
// The Metric name and description
- static final String MAX_IN_USE_SESSIONS = "cloud.google.com/java/spanner/max_in_use_sessions";
- static final String MAX_ALLOWED_SESSIONS = "cloud.google.com/java/spanner/max_allowed_sessions";
- static final String GET_SESSION_TIMEOUTS = "cloud.google.com/java/spanner/get_session_timeouts";
- static final String NUM_ACQUIRED_SESSIONS = "cloud.google.com/java/spanner/num_acquired_sessions";
- static final String NUM_RELEASED_SESSIONS = "cloud.google.com/java/spanner/num_released_sessions";
- static final String NUM_SESSIONS_IN_POOL = "cloud.google.com/java/spanner/num_sessions_in_pool";
+ static final String MAX_IN_USE_SESSIONS = "spanner/max_in_use_sessions";
+ static final String MAX_ALLOWED_SESSIONS = "spanner/max_allowed_sessions";
+ static final String GET_SESSION_TIMEOUTS = "spanner/get_session_timeouts";
+ static final String NUM_ACQUIRED_SESSIONS = "spanner/num_acquired_sessions";
+ static final String NUM_RELEASED_SESSIONS = "spanner/num_released_sessions";
+ static final String NUM_SESSIONS_IN_POOL = "spanner/num_sessions_in_pool";
+ static final String NUM_SESSIONS_IN_USE = "spanner/num_in_use_sessions";
+ static final String NUM_SESSIONS_AVAILABLE = "spanner/num_available_sessions";
+ static final String SESSIONS_TYPE = "session_type";
static final String MAX_IN_USE_SESSIONS_DESCRIPTION =
"The maximum number of sessions in use during the last 10 minute interval.";
@@ -84,4 +91,11 @@ class MetricRegistryConstants {
static final String NUM_RELEASED_SESSIONS_DESCRIPTION =
"The number of sessions released by the user and pool maintainer.";
static final String NUM_SESSIONS_IN_POOL_DESCRIPTION = "The number of sessions in the pool.";
+
+ static final String SPANNER_GFE_LATENCY = "spanner/gfe_latency";
+ static final String SPANNER_GFE_LATENCY_DESCRIPTION =
+ "Latency between Google's network receiving an RPC and reading back the first byte of the response";
+ static final String SPANNER_GFE_HEADER_MISSING_COUNT = "spanner/gfe_header_missing_count";
+ static final String SPANNER_GFE_HEADER_MISSING_COUNT_DESCRIPTION =
+ "Number of RPC responses received without the server-timing header, most likely means that the RPC never reached Google's network";
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenCensusScope.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenCensusScope.java
new file mode 100644
index 00000000000..81c1db16571
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenCensusScope.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner;
+
+import io.opencensus.common.Scope;
+
+class OpenCensusScope implements IScope {
+
+ private final Scope openCensusScope;
+
+ OpenCensusScope(Scope openCensusScope) {
+ this.openCensusScope = openCensusScope;
+ }
+
+ @Override
+ public void close() {
+ openCensusScope.close();
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenCensusSpan.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenCensusSpan.java
new file mode 100644
index 00000000000..86e43778f31
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenCensusSpan.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner;
+
+import com.google.common.collect.ImmutableMap;
+import io.opencensus.contrib.grpc.util.StatusConverter;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.EndSpanOptions;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.Status;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OpenCensusSpan implements ISpan {
+
+ static final EndSpanOptions END_SPAN_OPTIONS =
+ EndSpanOptions.builder().setSampleToLocalSpanStore(true).build();
+ private final Span openCensusSpan;
+
+ public OpenCensusSpan(Span openCensusSpan) {
+ this.openCensusSpan = openCensusSpan;
+ }
+
+ Span getOpenCensusSpan() {
+ return openCensusSpan;
+ }
+
+ private ImmutableMap getOpenCensusExceptionAnnotations(Throwable e) {
+ if (e instanceof SpannerException) {
+ return ImmutableMap.of(
+ "Status",
+ AttributeValue.stringAttributeValue(((SpannerException) e).getErrorCode().toString()));
+ }
+ return ImmutableMap.of();
+ }
+
+ @Override
+ public void addAnnotation(String message, Map attributes) {
+ Map ocAttributeValues = new HashMap<>();
+ for (Map.Entry entry : attributes.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value instanceof String) {
+ ocAttributeValues.put(key, AttributeValue.stringAttributeValue((String) value));
+ } else if (value instanceof Long) {
+ ocAttributeValues.put(key, AttributeValue.longAttributeValue((Long) value));
+ }
+ }
+
+ if (ocAttributeValues.size() > 0) {
+ openCensusSpan.addAnnotation(message, ocAttributeValues);
+ }
+ }
+
+ @Override
+ public void addAnnotation(String message) {
+ openCensusSpan.addAnnotation(message);
+ }
+
+ @Override
+ public void addAnnotation(String message, String key, String value) {
+ openCensusSpan.addAnnotation(
+ message, ImmutableMap.of(key, AttributeValue.stringAttributeValue(value)));
+ }
+
+ @Override
+ public void addAnnotation(String message, String key, long value) {
+ openCensusSpan.addAnnotation(
+ message, ImmutableMap.of(key, AttributeValue.longAttributeValue(value)));
+ }
+
+ @Override
+ public void addAnnotation(String message, Throwable e) {
+ openCensusSpan.addAnnotation(message, this.getOpenCensusExceptionAnnotations(e));
+ }
+
+ @Override
+ public void setStatus(Throwable e) {
+ if (e instanceof SpannerException) {
+ openCensusSpan.setStatus(
+ StatusConverter.fromGrpcStatus(((SpannerException) e).getErrorCode().getGrpcStatus())
+ .withDescription(e.getMessage()));
+ } else {
+ openCensusSpan.setStatus(Status.INTERNAL.withDescription(e.getMessage()));
+ }
+ }
+
+ @Override
+ public void setStatus(ErrorCode errorCode) {
+ openCensusSpan.setStatus(StatusConverter.fromGrpcStatus(errorCode.getGrpcStatus()));
+ }
+
+ @Override
+ public void end() {
+ openCensusSpan.end(END_SPAN_OPTIONS);
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenTelemetryScope.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenTelemetryScope.java
new file mode 100644
index 00000000000..6766bc54e6b
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenTelemetryScope.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner;
+
+import io.opentelemetry.context.Scope;
+
+class OpenTelemetryScope implements IScope {
+
+ private final Scope openTelemetryScope;
+
+ OpenTelemetryScope(Scope openTelemetryScope) {
+ this.openTelemetryScope = openTelemetryScope;
+ }
+
+ @Override
+ public void close() {
+ openTelemetryScope.close();
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenTelemetrySpan.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenTelemetrySpan.java
new file mode 100644
index 00000000000..98e1540e760
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/OpenTelemetrySpan.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.StatusCode;
+import java.util.Map;
+
+class OpenTelemetrySpan implements ISpan {
+
+ private final io.opentelemetry.api.trace.Span openTelemetrySpan;
+
+ OpenTelemetrySpan(Span openTelemetrySpan) {
+ this.openTelemetrySpan = openTelemetrySpan;
+ }
+
+ Span getOpenTelemetrySpan() {
+ return openTelemetrySpan;
+ }
+
+ @Override
+ public void addAnnotation(String message, Map attributes) {
+ AttributesBuilder otAttributesBuilder = Attributes.builder();
+ for (Map.Entry entry : attributes.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value instanceof String) {
+ otAttributesBuilder.put(key, (String) value);
+ } else if (value instanceof Long) {
+ otAttributesBuilder.put(key, (Long) value);
+ }
+ }
+ openTelemetrySpan.addEvent(message, otAttributesBuilder.build());
+ }
+
+ @Override
+ public void addAnnotation(String message) {
+ openTelemetrySpan.addEvent(message);
+ }
+
+ @Override
+ public void addAnnotation(String message, String key, String value) {
+ openTelemetrySpan.addEvent(message, Attributes.builder().put(key, value).build());
+ }
+
+ @Override
+ public void addAnnotation(String message, String key, long value) {
+ openTelemetrySpan.addEvent(message, Attributes.builder().put(key, value).build());
+ }
+
+ @Override
+ public void addAnnotation(String message, Throwable e) {
+ openTelemetrySpan.addEvent(message, this.createOpenTelemetryExceptionAnnotations(e));
+ }
+
+ @Override
+ public void setStatus(Throwable e) {
+ if (e instanceof SpannerException) {
+ openTelemetrySpan.setStatus(StatusCode.ERROR, ((SpannerException) e).getErrorCode().name());
+ } else {
+ openTelemetrySpan.setStatus(StatusCode.ERROR, ErrorCode.INTERNAL.name());
+ }
+ openTelemetrySpan.recordException(e);
+ }
+
+ @Override
+ public void setStatus(ErrorCode errorCode) {
+ openTelemetrySpan.setStatus(StatusCode.ERROR, errorCode.name());
+ }
+
+ @Override
+ public void end() {
+ openTelemetrySpan.end();
+ }
+
+ private Attributes createOpenTelemetryExceptionAnnotations(Throwable e) {
+ AttributesBuilder attributesBuilder = Attributes.builder();
+ if (e instanceof SpannerException) {
+ attributesBuilder.put("Status", ((SpannerException) e).getErrorCode().toString());
+ }
+ return attributesBuilder.build();
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java
index dda12b60d64..57feabbfcca 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Options.java
@@ -20,6 +20,7 @@
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.RequestOptions.Priority;
import java.io.Serializable;
+import java.time.Duration;
import java.util.Objects;
/** Specifies options for various spanner operations */
@@ -140,6 +141,11 @@ public static ReadQueryUpdateTransactionOption priority(RpcPriority priority) {
return new PriorityOption(priority);
}
+ public static ReadQueryUpdateTransactionOption maxCommitDelay(Duration maxCommitDelay) {
+ Preconditions.checkArgument(!maxCommitDelay.isNegative(), "maxCommitDelay should be positive");
+ return new MaxCommitDelayOption(maxCommitDelay);
+ }
+
/**
* Specifying this will cause the reads, queries, updates and writes operations statistics
* collection to be grouped by tag.
@@ -247,6 +253,21 @@ void appendToOptions(Options options) {
static final CommitStatsOption COMMIT_STATS_OPTION = new CommitStatsOption();
+ /** Option to request {@link MaxCommitDelayOption} for read/write transactions. */
+ static final class MaxCommitDelayOption extends InternalOption
+ implements ReadQueryUpdateTransactionOption {
+ final Duration maxCommitDelay;
+
+ MaxCommitDelayOption(Duration maxCommitDelay) {
+ this.maxCommitDelay = maxCommitDelay;
+ }
+
+ @Override
+ void appendToOptions(Options options) {
+ options.maxCommitDelay = maxCommitDelay;
+ }
+ }
+
/** Option to request Optimistic Concurrency Control for read/write transactions. */
static final class OptimisticLockOption extends InternalOption implements TransactionOption {
@Override
@@ -354,6 +375,9 @@ void appendToOptions(Options options) {
}
private boolean withCommitStats;
+
+ private Duration maxCommitDelay;
+
private Long limit;
private Integer prefetchChunks;
private Integer bufferRows;
@@ -375,6 +399,14 @@ boolean withCommitStats() {
return withCommitStats;
}
+ boolean hasMaxCommitDelay() {
+ return maxCommitDelay != null;
+ }
+
+ Duration maxCommitDelay() {
+ return maxCommitDelay;
+ }
+
boolean hasLimit() {
return limit != null;
}
@@ -481,6 +513,9 @@ public String toString() {
if (withCommitStats) {
b.append("withCommitStats: ").append(withCommitStats).append(' ');
}
+ if (maxCommitDelay != null) {
+ b.append("maxCommitDelay: ").append(maxCommitDelay).append(' ');
+ }
if (limit != null) {
b.append("limit: ").append(limit).append(' ');
}
@@ -533,6 +568,7 @@ public boolean equals(Object o) {
Options that = (Options) o;
return Objects.equals(withCommitStats, that.withCommitStats)
+ && Objects.equals(maxCommitDelay, that.maxCommitDelay)
&& (!hasLimit() && !that.hasLimit()
|| hasLimit() && that.hasLimit() && Objects.equals(limit(), that.limit()))
&& (!hasPrefetchChunks() && !that.hasPrefetchChunks()
@@ -562,6 +598,9 @@ public int hashCode() {
if (withCommitStats) {
result = 31 * result + 1231;
}
+ if (maxCommitDelay != null) {
+ result = 31 * result + maxCommitDelay.hashCode();
+ }
if (limit != null) {
result = 31 * result + limit.hashCode();
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
index 36991b18c3d..cabc270566c 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
@@ -18,6 +18,7 @@
import static com.google.common.base.Preconditions.checkState;
+import com.google.api.core.InternalApi;
import com.google.api.gax.grpc.GrpcStatusCode;
import com.google.api.gax.rpc.AbortedException;
import com.google.api.gax.rpc.DeadlineExceededException;
@@ -38,7 +39,6 @@
import com.google.spanner.v1.TransactionOptions;
import com.google.spanner.v1.TransactionSelector;
import io.grpc.Status;
-import io.opencensus.trace.Span;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@@ -46,6 +46,7 @@
import org.threeten.bp.Duration;
import org.threeten.bp.temporal.ChronoUnit;
+@InternalApi
public class PartitionedDmlTransaction implements SessionImpl.SessionTransaction {
private static final Logger LOGGER = Logger.getLogger(PartitionedDmlTransaction.class.getName());
@@ -137,9 +138,9 @@ public void invalidate() {
isValid = false;
}
- // No-op method needed to implement SessionTransaction interface.
+ /** No-op method needed to implement SessionTransaction interface. */
@Override
- public void setSpan(Span span) {}
+ public void setSpan(ISpan span) {}
private Duration tryUpdateTimeout(final Duration timeout, final Stopwatch stopwatch) {
final Duration remainingTimeout =
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionClient.java
index 474d99671e8..b294ef33395 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionClient.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionClient.java
@@ -25,8 +25,6 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
-import io.opencensus.common.Scope;
-import io.opencensus.trace.Span;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -127,16 +125,17 @@ private BatchCreateSessionsRunnable(
public void run() {
List sessions;
int remainingSessionsToCreate = sessionCount;
- Span span = SpannerImpl.tracer.spanBuilder(SpannerImpl.BATCH_CREATE_SESSIONS).startSpan();
- try (Scope s = SpannerImpl.tracer.withSpan(span)) {
- SpannerImpl.tracer
+ ISpan span = spanner.getTracer().spanBuilder(SpannerImpl.BATCH_CREATE_SESSIONS);
+ try (IScope s = spanner.getTracer().withSpan(span)) {
+ spanner
+ .getTracer()
.getCurrentSpan()
.addAnnotation(String.format("Creating %d sessions", sessionCount));
while (remainingSessionsToCreate > 0) {
try {
sessions = internalBatchCreateSessions(remainingSessionsToCreate, channelHint);
} catch (Throwable t) {
- TraceUtil.setWithFailure(SpannerImpl.tracer.getCurrentSpan(), t);
+ spanner.getTracer().getCurrentSpan().setStatus(t);
consumer.onSessionCreateFailure(t, remainingSessionsToCreate);
break;
}
@@ -146,7 +145,7 @@ public void run() {
remainingSessionsToCreate -= sessions.size();
}
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
}
@@ -206,8 +205,8 @@ SessionImpl createSession() {
synchronized (this) {
options = optionMap(SessionOption.channelHint(sessionChannelCounter++));
}
- Span span = SpannerImpl.tracer.spanBuilder(SpannerImpl.CREATE_SESSION).startSpan();
- try (Scope s = SpannerImpl.tracer.withSpan(span)) {
+ ISpan span = spanner.getTracer().spanBuilder(SpannerImpl.CREATE_SESSION);
+ try (IScope s = spanner.getTracer().withSpan(span)) {
com.google.spanner.v1.Session session =
spanner
.getRpc()
@@ -218,10 +217,10 @@ SessionImpl createSession() {
options);
return new SessionImpl(spanner, session.getName(), options);
} catch (RuntimeException e) {
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw e;
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
@@ -290,13 +289,13 @@ void asyncBatchCreateSessions(
private List internalBatchCreateSessions(
final int sessionCount, final long channelHint) throws SpannerException {
final Map options = optionMap(SessionOption.channelHint(channelHint));
- Span parent = SpannerImpl.tracer.getCurrentSpan();
- Span span =
- SpannerImpl.tracer
- .spanBuilderWithExplicitParent(SpannerImpl.BATCH_CREATE_SESSIONS_REQUEST, parent)
- .startSpan();
+ ISpan parent = spanner.getTracer().getCurrentSpan();
+ ISpan span =
+ spanner
+ .getTracer()
+ .spanBuilderWithExplicitParent(SpannerImpl.BATCH_CREATE_SESSIONS_REQUEST, parent);
span.addAnnotation(String.format("Requesting %d sessions", sessionCount));
- try (Scope s = SpannerImpl.tracer.withSpan(span)) {
+ try (IScope s = spanner.getTracer().withSpan(span)) {
List sessions =
spanner
.getRpc()
@@ -309,14 +308,15 @@ private List internalBatchCreateSessions(
span.addAnnotation(
String.format(
"Request for %d sessions returned %d sessions", sessionCount, sessions.size()));
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
List res = new ArrayList<>(sessionCount);
for (com.google.spanner.v1.Session session : sessions) {
res.add(new SessionImpl(spanner, session.getName(), options));
}
return res;
} catch (RuntimeException e) {
- TraceUtil.endSpanWithFailure(span, e);
+ span.setStatus(e);
+ span.end();
throw e;
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
index 53bf37feb05..29928f61cec 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java
@@ -35,6 +35,7 @@
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
+import com.google.protobuf.Duration;
import com.google.protobuf.Empty;
import com.google.spanner.v1.BatchWriteRequest;
import com.google.spanner.v1.BatchWriteResponse;
@@ -43,10 +44,6 @@
import com.google.spanner.v1.RequestOptions;
import com.google.spanner.v1.Transaction;
import com.google.spanner.v1.TransactionOptions;
-import io.opencensus.common.Scope;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Tracer;
-import io.opencensus.trace.Tracing;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -60,7 +57,7 @@
* users need not be aware of the actual session management, pooling and handling.
*/
class SessionImpl implements Session {
- private static final Tracer tracer = Tracing.getTracer();
+ private final TraceWrapper tracer;
/** Keep track of running transactions on this session per thread. */
static final ThreadLocal hasPendingTransaction = ThreadLocal.withInitial(() -> false);
@@ -86,10 +83,12 @@ static TransactionOptions createReadWriteTransactionOptions(Options options) {
* only have one such transaction active at a time.
*/
interface SessionTransaction {
+
/** Invalidates the transaction, generally because a new one has been started on the session. */
void invalidate();
+
/** Registers the current span on the transaction. */
- void setSpan(Span span);
+ void setSpan(ISpan span);
}
private final SpannerImpl spanner;
@@ -98,11 +97,12 @@ interface SessionTransaction {
private SessionTransaction activeTransaction;
ByteString readyTransactionId;
private final Map options;
- private Span currentSpan;
private volatile Instant lastUseTime;
+ private ISpan currentSpan;
SessionImpl(SpannerImpl spanner, String name, Map options) {
this.spanner = spanner;
+ this.tracer = spanner.getTracer();
this.options = options;
this.name = checkNotNull(name);
this.databaseId = SessionId.of(name).getDatabaseId();
@@ -118,11 +118,11 @@ public String getName() {
return options;
}
- void setCurrentSpan(Span span) {
+ void setCurrentSpan(ISpan span) {
currentSpan = span;
}
- Span getCurrentSpan() {
+ ISpan getCurrentSpan() {
return currentSpan;
}
@@ -176,29 +176,37 @@ public CommitResponse writeAtLeastOnceWithOptions(
setActive(null);
List mutationsProto = new ArrayList<>();
Mutation.toProto(mutations, mutationsProto);
+ Options options = Options.fromTransactionOptions(transactionOptions);
final CommitRequest.Builder requestBuilder =
CommitRequest.newBuilder()
.setSession(name)
- .setReturnCommitStats(
- Options.fromTransactionOptions(transactionOptions).withCommitStats())
+ .setReturnCommitStats(options.withCommitStats())
.addAllMutations(mutationsProto)
.setSingleUseTransaction(
TransactionOptions.newBuilder()
.setReadWrite(TransactionOptions.ReadWrite.getDefaultInstance()));
+ if (options.hasMaxCommitDelay()) {
+ requestBuilder.setMaxCommitDelay(
+ Duration.newBuilder()
+ .setSeconds(options.maxCommitDelay().getSeconds())
+ .setNanos(options.maxCommitDelay().getNano())
+ .build());
+ }
RequestOptions commitRequestOptions = getRequestOptions(transactionOptions);
+
if (commitRequestOptions != null) {
requestBuilder.setRequestOptions(commitRequestOptions);
}
CommitRequest request = requestBuilder.build();
- Span span = tracer.spanBuilder(SpannerImpl.COMMIT).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(SpannerImpl.COMMIT);
+ try (IScope s = tracer.withSpan(span)) {
return SpannerRetryHelper.runTxWithRetriesOnAborted(
() -> new CommitResponse(spanner.getRpc().commit(request, this.options)));
} catch (RuntimeException e) {
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw e;
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
@@ -230,14 +238,14 @@ public ServerStream batchWriteAtLeastOnce(
if (batchWriteRequestOptions != null) {
requestBuilder.setRequestOptions(batchWriteRequestOptions);
}
- Span span = tracer.spanBuilder(SpannerImpl.BATCH_WRITE).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(SpannerImpl.BATCH_WRITE);
+ try (IScope s = tracer.withSpan(span)) {
return spanner.getRpc().batchWriteAtLeastOnce(requestBuilder.build(), this.options);
} catch (Throwable e) {
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw SpannerExceptionFactory.newSpannerException(e);
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
@@ -257,6 +265,7 @@ public ReadContext singleUse(TimestampBound bound) {
.setDefaultPrefetchChunks(spanner.getDefaultPrefetchChunks())
.setDefaultDirectedReadOptions(spanner.getOptions().getDirectedReadOptions())
.setSpan(currentSpan)
+ .setTracer(tracer)
.setExecutorProvider(spanner.getAsyncExecutorProvider())
.build());
}
@@ -277,6 +286,7 @@ public ReadOnlyTransaction singleUseReadOnlyTransaction(TimestampBound bound) {
.setDefaultPrefetchChunks(spanner.getDefaultPrefetchChunks())
.setDefaultDirectedReadOptions(spanner.getOptions().getDirectedReadOptions())
.setSpan(currentSpan)
+ .setTracer(tracer)
.setExecutorProvider(spanner.getAsyncExecutorProvider())
.buildSingleUseReadOnlyTransaction());
}
@@ -297,6 +307,7 @@ public ReadOnlyTransaction readOnlyTransaction(TimestampBound bound) {
.setDefaultPrefetchChunks(spanner.getDefaultPrefetchChunks())
.setDefaultDirectedReadOptions(spanner.getOptions().getDirectedReadOptions())
.setSpan(currentSpan)
+ .setTracer(tracer)
.setExecutorProvider(spanner.getAsyncExecutorProvider())
.build());
}
@@ -313,7 +324,7 @@ public AsyncRunner runAsync(TransactionOption... options) {
@Override
public TransactionManager transactionManager(TransactionOption... options) {
- return new TransactionManagerImpl(this, currentSpan, options);
+ return new TransactionManagerImpl(this, currentSpan, tracer, options);
}
@Override
@@ -334,14 +345,14 @@ public ApiFuture asyncClose() {
@Override
public void close() {
- Span span = tracer.spanBuilder(SpannerImpl.DELETE_SESSION).startSpan();
- try (Scope s = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(SpannerImpl.DELETE_SESSION);
+ try (IScope s = tracer.withSpan(span)) {
spanner.getRpc().deleteSession(name, options);
} catch (RuntimeException e) {
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw e;
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
@@ -361,7 +372,7 @@ ApiFuture beginTransactionAsync(boolean routeToLeader) {
ApiFuture beginTransactionAsync(Options transactionOptions, boolean routeToLeader) {
final SettableApiFuture res = SettableApiFuture.create();
- final Span span = tracer.spanBuilder(SpannerImpl.BEGIN_TRANSACTION).startSpan();
+ final ISpan span = tracer.spanBuilder(SpannerImpl.BEGIN_TRANSACTION);
final BeginTransactionRequest request =
BeginTransactionRequest.newBuilder()
.setSession(name)
@@ -370,30 +381,31 @@ ApiFuture beginTransactionAsync(Options transactionOptions, boolean
final ApiFuture requestFuture =
spanner.getRpc().beginTransactionAsync(request, options, routeToLeader);
requestFuture.addListener(
- tracer.withSpan(
- span,
- () -> {
- try {
- Transaction txn = requestFuture.get();
- if (txn.getId().isEmpty()) {
- throw newSpannerException(
- ErrorCode.INTERNAL, "Missing id in transaction\n" + getName());
- }
- span.end(TraceUtil.END_SPAN_OPTIONS);
- res.set(txn.getId());
- } catch (ExecutionException e) {
- TraceUtil.endSpanWithFailure(span, e);
- res.setException(
- SpannerExceptionFactory.newSpannerException(
- e.getCause() == null ? e : e.getCause()));
- } catch (InterruptedException e) {
- TraceUtil.endSpanWithFailure(span, e);
- res.setException(SpannerExceptionFactory.propagateInterrupt(e));
- } catch (Exception e) {
- TraceUtil.endSpanWithFailure(span, e);
- res.setException(e);
- }
- }),
+ () -> {
+ try (IScope s = tracer.withSpan(span)) {
+ Transaction txn = requestFuture.get();
+ if (txn.getId().isEmpty()) {
+ throw newSpannerException(
+ ErrorCode.INTERNAL, "Missing id in transaction\n" + getName());
+ }
+ span.end();
+ res.set(txn.getId());
+ } catch (ExecutionException e) {
+ span.setStatus(e);
+ span.end();
+ res.setException(
+ SpannerExceptionFactory.newSpannerException(
+ e.getCause() == null ? e : e.getCause()));
+ } catch (InterruptedException e) {
+ span.setStatus(e);
+ span.end();
+ res.setException(SpannerExceptionFactory.propagateInterrupt(e));
+ } catch (Exception e) {
+ span.setStatus(e);
+ span.end();
+ res.setException(e);
+ }
+ },
MoreExecutors.directExecutor());
return res;
}
@@ -412,6 +424,7 @@ TransactionContextImpl newTransaction(Options options) {
.setDefaultQueryOptions(spanner.getDefaultQueryOptions(databaseId))
.setDefaultPrefetchChunks(spanner.getDefaultPrefetchChunks())
.setSpan(currentSpan)
+ .setTracer(tracer)
.setExecutorProvider(spanner.getAsyncExecutorProvider())
.setClock(poolMaintainerClock == null ? new Clock() : poolMaintainerClock)
.build();
@@ -434,4 +447,8 @@ T setActive(@Nullable T ctx) {
boolean hasReadyTransaction() {
return readyTransactionId != null;
}
+
+ TraceWrapper getTracer() {
+ return tracer;
+ }
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
index 54a0a292cd8..cc24dd2ba0f 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
@@ -22,17 +22,21 @@
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_ALLOWED_SESSIONS_DESCRIPTION;
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_IN_USE_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.MAX_IN_USE_SESSIONS_DESCRIPTION;
+import static com.google.cloud.spanner.MetricRegistryConstants.METRIC_PREFIX;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_ACQUIRED_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_ACQUIRED_SESSIONS_DESCRIPTION;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_IN_USE_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_READ_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_RELEASED_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_RELEASED_SESSIONS_DESCRIPTION;
+import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_AVAILABLE;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_BEING_PREPARED;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_IN_POOL;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_IN_POOL_DESCRIPTION;
+import static com.google.cloud.spanner.MetricRegistryConstants.NUM_SESSIONS_IN_USE;
import static com.google.cloud.spanner.MetricRegistryConstants.NUM_WRITE_SESSIONS;
import static com.google.cloud.spanner.MetricRegistryConstants.SESSIONS_TIMEOUTS_DESCRIPTION;
+import static com.google.cloud.spanner.MetricRegistryConstants.SESSIONS_TYPE;
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_DEFAULT_LABEL_VALUES;
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS;
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS_WITH_TYPE;
@@ -62,7 +66,6 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ForwardingListenableFuture;
import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture;
import com.google.common.util.concurrent.ListenableFuture;
@@ -71,20 +74,16 @@
import com.google.protobuf.Empty;
import com.google.spanner.v1.BatchWriteResponse;
import com.google.spanner.v1.ResultSetStats;
-import io.opencensus.common.Scope;
import io.opencensus.metrics.DerivedLongCumulative;
import io.opencensus.metrics.DerivedLongGauge;
import io.opencensus.metrics.LabelValue;
import io.opencensus.metrics.MetricOptions;
import io.opencensus.metrics.MetricRegistry;
import io.opencensus.metrics.Metrics;
-import io.opencensus.trace.Annotation;
-import io.opencensus.trace.AttributeValue;
-import io.opencensus.trace.BlankSpan;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Status;
-import io.opencensus.trace.Tracer;
-import io.opencensus.trace.Tracing;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.Meter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
@@ -117,7 +116,7 @@
class SessionPool {
private static final Logger logger = Logger.getLogger(SessionPool.class.getName());
- private static final Tracer tracer = Tracing.getTracer();
+ private final TraceWrapper tracer;
static final String WAIT_FOR_SESSION = "SessionPool.WaitForSession";
/**
@@ -1094,7 +1093,7 @@ private enum SessionState {
}
private PooledSessionFuture createPooledSessionFuture(
- ListenableFuture future, Span span) {
+ ListenableFuture future, ISpan span) {
return new PooledSessionFuture(future, span);
}
@@ -1103,10 +1102,10 @@ class PooledSessionFuture extends SimpleForwardingListenableFuture delegate, Span span) {
+ PooledSessionFuture(ListenableFuture delegate, ISpan span) {
super(delegate);
this.span = span;
}
@@ -1338,7 +1337,7 @@ PooledSession get(final boolean eligibleForLongRunning) {
}
if (res != null) {
res.markBusy(span);
- span.addAnnotation(sessionAnnotation(res));
+ span.addAnnotation("Using Session", "sessionId", res.getName());
synchronized (lock) {
incrementNumSessionsInUse();
checkedOutSessions.add(this);
@@ -1572,8 +1571,8 @@ public void prepareReadWriteTransaction() {
private void keepAlive() {
markUsed();
- final Span previousSpan = delegate.getCurrentSpan();
- delegate.setCurrentSpan(BlankSpan.INSTANCE);
+ final ISpan previousSpan = delegate.getCurrentSpan();
+ delegate.setCurrentSpan(tracer.getBlankSpan());
try (ResultSet resultSet =
delegate
.singleUse(TimestampBound.ofMaxStaleness(60, TimeUnit.SECONDS))
@@ -1612,7 +1611,7 @@ private Dialect determineDialect() {
}
}
- private void markBusy(Span span) {
+ private void markBusy(ISpan span) {
this.delegate.setCurrentSpan(span);
this.state = SessionState.BUSY;
}
@@ -1652,14 +1651,14 @@ private void put(SpannerException e) {
public PooledSession get() {
long currentTimeout = options.getInitialWaitForSessionTimeoutMillis();
while (true) {
- Span span = tracer.spanBuilder(WAIT_FOR_SESSION).startSpan();
- try (Scope waitScope = tracer.withSpan(span)) {
+ ISpan span = tracer.spanBuilder(WAIT_FOR_SESSION);
+ try (IScope waitScope = tracer.withSpan(span)) {
PooledSession s =
pollUninterruptiblyWithTimeout(currentTimeout, options.getAcquireSessionTimeout());
if (s == null) {
// Set the status to DEADLINE_EXCEEDED and retry.
numWaiterTimeouts.incrementAndGet();
- tracer.getCurrentSpan().setStatus(Status.DEADLINE_EXCEEDED);
+ tracer.getCurrentSpan().setStatus(ErrorCode.DEADLINE_EXCEEDED);
currentTimeout = Math.min(currentTimeout * 2, MAX_SESSION_WAIT_TIMEOUT);
} else {
return s;
@@ -1668,12 +1667,12 @@ public PooledSession get() {
if (e instanceof SpannerException
&& ErrorCode.RESOURCE_EXHAUSTED.equals(((SpannerException) e).getErrorCode())) {
numWaiterTimeouts.incrementAndGet();
- tracer.getCurrentSpan().setStatus(Status.RESOURCE_EXHAUSTED);
+ tracer.getCurrentSpan().setStatus(ErrorCode.RESOURCE_EXHAUSTED);
}
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw e;
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
}
@@ -2081,7 +2080,11 @@ enum Position {
* be created.
*/
static SessionPool createPool(
- SpannerOptions spannerOptions, SessionClient sessionClient, List labelValues) {
+ SpannerOptions spannerOptions,
+ SessionClient sessionClient,
+ TraceWrapper tracer,
+ List labelValues,
+ Attributes attributes) {
final SessionPoolOptions sessionPoolOptions = spannerOptions.getSessionPoolOptions();
// A clock instance is passed in {@code SessionPoolOptions} in order to allow mocking via tests.
@@ -2094,14 +2097,26 @@ static SessionPool createPool(
poolMaintainerClock == null ? new Clock() : poolMaintainerClock,
Position.RANDOM,
Metrics.getMetricRegistry(),
- labelValues);
+ tracer,
+ labelValues,
+ spannerOptions.getOpenTelemetry(),
+ attributes);
}
static SessionPool createPool(
SessionPoolOptions poolOptions,
ExecutorFactory executorFactory,
- SessionClient sessionClient) {
- return createPool(poolOptions, executorFactory, sessionClient, new Clock(), Position.RANDOM);
+ SessionClient sessionClient,
+ TraceWrapper tracer,
+ OpenTelemetry openTelemetry) {
+ return createPool(
+ poolOptions,
+ executorFactory,
+ sessionClient,
+ new Clock(),
+ Position.RANDOM,
+ tracer,
+ openTelemetry);
}
static SessionPool createPool(
@@ -2109,7 +2124,9 @@ static SessionPool createPool(
ExecutorFactory executorFactory,
SessionClient sessionClient,
Clock clock,
- Position initialReleasePosition) {
+ Position initialReleasePosition,
+ TraceWrapper tracer,
+ OpenTelemetry openTelemetry) {
return createPool(
poolOptions,
null,
@@ -2118,7 +2135,10 @@ static SessionPool createPool(
clock,
initialReleasePosition,
Metrics.getMetricRegistry(),
- SPANNER_DEFAULT_LABEL_VALUES);
+ tracer,
+ SPANNER_DEFAULT_LABEL_VALUES,
+ openTelemetry,
+ null);
}
static SessionPool createPool(
@@ -2129,7 +2149,10 @@ static SessionPool createPool(
Clock clock,
Position initialReleasePosition,
MetricRegistry metricRegistry,
- List labelValues) {
+ TraceWrapper tracer,
+ List labelValues,
+ OpenTelemetry openTelemetry,
+ Attributes attributes) {
SessionPool pool =
new SessionPool(
poolOptions,
@@ -2140,7 +2163,10 @@ static SessionPool createPool(
clock,
initialReleasePosition,
metricRegistry,
- labelValues);
+ tracer,
+ labelValues,
+ openTelemetry,
+ attributes);
pool.initPool();
return pool;
}
@@ -2154,7 +2180,10 @@ private SessionPool(
Clock clock,
Position initialReleasePosition,
MetricRegistry metricRegistry,
- List labelValues) {
+ TraceWrapper tracer,
+ List labelValues,
+ OpenTelemetry openTelemetry,
+ Attributes attributes) {
this.options = options;
this.databaseRole = databaseRole;
this.executorFactory = executorFactory;
@@ -2163,7 +2192,9 @@ private SessionPool(
this.clock = clock;
this.initialReleasePosition = initialReleasePosition;
this.poolMaintainer = new PoolMaintainer();
- this.initMetricsCollection(metricRegistry, labelValues);
+ this.tracer = tracer;
+ this.initOpenCensusMetricsCollection(metricRegistry, labelValues);
+ this.initOpenTelemetryMetricsCollection(openTelemetry, attributes);
this.waitOnMinSessionsLatch =
options.getMinSessions() > 0 ? new CountDownLatch(1) : new CountDownLatch(0);
}
@@ -2351,7 +2382,7 @@ boolean isValid() {
*
*/
PooledSessionFuture getSession() throws SpannerException {
- Span span = Tracing.getTracer().getCurrentSpan();
+ ISpan span = tracer.getCurrentSpan();
span.addAnnotation("Acquiring session");
WaiterFuture waiter = null;
PooledSession sess = null;
@@ -2383,7 +2414,7 @@ PooledSessionFuture getSession() throws SpannerException {
}
private PooledSessionFuture checkoutSession(
- final Span span, final PooledSession readySession, WaiterFuture waiter) {
+ final ISpan span, final PooledSession readySession, WaiterFuture waiter) {
ListenableFuture sessionFuture;
if (waiter != null) {
logger.log(
@@ -2416,12 +2447,6 @@ PooledSessionFuture replaceSession(SessionNotFoundException e, PooledSessionFutu
}
}
- private Annotation sessionAnnotation(Session session) {
- AttributeValue sessionId = AttributeValue.stringAttributeValue(session.getName());
- return Annotation.fromDescriptionAndAttributes(
- "Using Session", ImmutableMap.of("sessionId", sessionId));
- }
-
private void incrementNumSessionsInUse() {
synchronized (lock) {
if (maxSessionsInUse < ++numSessionsInUse) {
@@ -2432,7 +2457,7 @@ private void incrementNumSessionsInUse() {
}
private void maybeCreateSession() {
- Span span = Tracing.getTracer().getCurrentSpan();
+ ISpan span = tracer.getCurrentSpan();
synchronized (lock) {
if (numWaiters() >= numSessionsBeingCreated) {
if (canCreateSession()) {
@@ -2799,13 +2824,17 @@ public void onSessionCreateFailure(Throwable t, int createFailureForSessionCount
}
/**
- * Initializes and creates Spanner session relevant metrics. When coupled with an exporter, it
- * allows users to monitor client behavior.
+ * Initializes and creates Spanner session relevant metrics using OpenCensus. When coupled with an
+ * exporter, it allows users to monitor client behavior.
*/
- private void initMetricsCollection(MetricRegistry metricRegistry, List labelValues) {
+ private void initOpenCensusMetricsCollection(
+ MetricRegistry metricRegistry, List labelValues) {
+ if (!SpannerOptions.isEnabledOpenCensusMetrics()) {
+ return;
+ }
DerivedLongGauge maxInUseSessionsMetric =
metricRegistry.addDerivedLongGauge(
- MAX_IN_USE_SESSIONS,
+ METRIC_PREFIX + MAX_IN_USE_SESSIONS,
MetricOptions.builder()
.setDescription(MAX_IN_USE_SESSIONS_DESCRIPTION)
.setUnit(COUNT)
@@ -2814,7 +2843,7 @@ private void initMetricsCollection(MetricRegistry metricRegistry, List 0L);
}
+
+ /**
+ * Initializes and creates Spanner session relevant metrics using OpenTelemetry. When coupled with
+ * an exporter, it allows users to monitor client behavior.
+ */
+ private void initOpenTelemetryMetricsCollection(
+ OpenTelemetry openTelemetry, Attributes attributes) {
+ if (openTelemetry == null || !SpannerOptions.isEnabledOpenTelemetryMetrics()) {
+ return;
+ }
+
+ Meter meter = openTelemetry.getMeter(MetricRegistryConstants.INSTRUMENTATION_SCOPE);
+ meter
+ .gaugeBuilder(MAX_ALLOWED_SESSIONS)
+ .setDescription(MAX_ALLOWED_SESSIONS_DESCRIPTION)
+ .setUnit(COUNT)
+ .buildWithCallback(
+ measurement -> {
+ // Although Max sessions is a constant value, OpenTelemetry requires to define this as
+ // a callback.
+ measurement.record(options.getMaxSessions(), attributes);
+ });
+
+ meter
+ .gaugeBuilder(MAX_IN_USE_SESSIONS)
+ .setDescription(MAX_IN_USE_SESSIONS_DESCRIPTION)
+ .setUnit(COUNT)
+ .buildWithCallback(
+ measurement -> {
+ measurement.record(this.maxSessionsInUse, attributes);
+ });
+
+ AttributesBuilder attributesBuilder;
+ if (attributes != null) {
+ attributesBuilder = attributes.toBuilder();
+ } else {
+ attributesBuilder = Attributes.builder();
+ }
+ Attributes attributesInUseSessions =
+ attributesBuilder.put(SESSIONS_TYPE, NUM_SESSIONS_IN_USE).build();
+ Attributes attributesAvailableSessions =
+ attributesBuilder.put(SESSIONS_TYPE, NUM_SESSIONS_AVAILABLE).build();
+ meter
+ .upDownCounterBuilder(NUM_SESSIONS_IN_POOL)
+ .setDescription(NUM_SESSIONS_IN_POOL_DESCRIPTION)
+ .setUnit(COUNT)
+ .buildWithCallback(
+ measurement -> {
+ measurement.record(this.numSessionsInUse, attributesInUseSessions);
+ measurement.record(this.sessions.size(), attributesAvailableSessions);
+ });
+
+ meter
+ .counterBuilder(GET_SESSION_TIMEOUTS)
+ .setDescription(SESSIONS_TIMEOUTS_DESCRIPTION)
+ .setUnit(COUNT)
+ .buildWithCallback(
+ measurement -> {
+ measurement.record(this.getNumWaiterTimeouts(), attributes);
+ });
+
+ meter
+ .counterBuilder(NUM_ACQUIRED_SESSIONS)
+ .setDescription(NUM_ACQUIRED_SESSIONS_DESCRIPTION)
+ .setUnit(COUNT)
+ .buildWithCallback(
+ measurement -> {
+ measurement.record(this.numSessionsAcquired, attributes);
+ });
+
+ meter
+ .counterBuilder(NUM_RELEASED_SESSIONS)
+ .setDescription(NUM_RELEASED_SESSIONS_DESCRIPTION)
+ .setUnit(COUNT)
+ .buildWithCallback(
+ measurement -> {
+ measurement.record(this.numSessionsReleased, attributes);
+ });
+ }
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
index f1a7888c62c..326a51d803e 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerImpl.java
@@ -37,8 +37,9 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
import io.opencensus.metrics.LabelValue;
-import io.opencensus.trace.Tracer;
import io.opencensus.trace.Tracing;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -55,7 +56,14 @@
/** Default implementation of the Cloud Spanner interface. */
class SpannerImpl extends BaseService implements Spanner {
private static final Logger logger = Logger.getLogger(SpannerImpl.class.getName());
- static final Tracer tracer = Tracing.getTracer();
+ final TraceWrapper tracer =
+ new TraceWrapper(
+ Tracing.getTracer(),
+ this.getOptions()
+ .getOpenTelemetry()
+ .getTracer(
+ MetricRegistryConstants.INSTRUMENTATION_SCOPE,
+ GaxProperties.getLibraryVersion(this.getOptions().getClass())));
static final String CREATE_SESSION = "CloudSpannerOperation.CreateSession";
static final String BATCH_CREATE_SESSIONS = "CloudSpannerOperation.BatchCreateSessions";
@@ -148,6 +156,10 @@ QueryOptions getDefaultQueryOptions(DatabaseId databaseId) {
return getOptions().getDefaultQueryOptions(databaseId);
}
+ TraceWrapper getTracer() {
+ return this.tracer;
+ }
+
/**
* Returns the {@link ExecutorProvider} to use for async methods that need a background executor.
*/
@@ -219,9 +231,19 @@ public DatabaseClient getDatabaseClient(DatabaseId db) {
LabelValue.create(db.getDatabase()),
LabelValue.create(db.getInstanceId().getName()),
LabelValue.create(GaxProperties.getLibraryVersion(getOptions().getClass())));
+
+ AttributesBuilder attributesBuilder = Attributes.builder();
+ attributesBuilder.put("client_id", clientId);
+ attributesBuilder.put("database", db.getDatabase());
+ attributesBuilder.put("instance_id", db.getInstanceId().getName());
+
SessionPool pool =
SessionPool.createPool(
- getOptions(), SpannerImpl.this.getSessionClient(db), labelValues);
+ getOptions(),
+ SpannerImpl.this.getSessionClient(db),
+ this.tracer,
+ labelValues,
+ attributesBuilder.build());
pool.maybeWaitOnMinSessions();
DatabaseClientImpl dbClient = createDatabaseClient(clientId, pool);
dbClients.put(db, dbClient);
@@ -232,7 +254,7 @@ public DatabaseClient getDatabaseClient(DatabaseId db) {
@VisibleForTesting
DatabaseClientImpl createDatabaseClient(String clientId, SessionPool pool) {
- return new DatabaseClientImpl(clientId, pool);
+ return new DatabaseClientImpl(clientId, pool, tracer);
}
@Override
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
index 0d9f3b85c19..9c6044aa938 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
@@ -19,6 +19,7 @@
import com.google.api.core.ApiFunction;
import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
+import com.google.api.core.ObsoleteApi;
import com.google.api.gax.core.ExecutorProvider;
import com.google.api.gax.grpc.GrpcCallContext;
import com.google.api.gax.grpc.GrpcInterceptorProvider;
@@ -61,6 +62,8 @@
import io.grpc.ExperimentalApi;
import io.grpc.ManagedChannelBuilder;
import io.grpc.MethodDescriptor;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -75,12 +78,15 @@
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import org.threeten.bp.Duration;
/** Options for the Cloud Spanner service. */
public class SpannerOptions extends ServiceOptions {
private static final long serialVersionUID = 2789571558532701170L;
private static SpannerEnvironment environment = SpannerEnvironmentImpl.INSTANCE;
+ private static boolean enableOpenCensusMetrics = true;
+ private static boolean enableOpenTelemetryMetrics = false;
private static final String JDBC_API_CLIENT_LIB_TOKEN = "sp-jdbc";
private static final String HIBERNATE_API_CLIENT_LIB_TOKEN = "sp-hib";
@@ -141,6 +147,17 @@ public class SpannerOptions extends ServiceOptions {
private final boolean attemptDirectPath;
private final DirectedReadOptions directedReadOptions;
private final boolean useVirtualThreads;
+ private final OpenTelemetry openTelemetry;
+
+ enum TracingFramework {
+ OPEN_CENSUS,
+ OPEN_TELEMETRY
+ }
+
+ private static final Object lock = new Object();
+
+ @GuardedBy("lock")
+ private static TracingFramework activeTracingFramework;
/** Interface that can be used to provide {@link CallCredentials} to {@link SpannerOptions}. */
public interface CallCredentialsProvider {
@@ -633,6 +650,7 @@ protected SpannerOptions(Builder builder) {
attemptDirectPath = builder.attemptDirectPath;
directedReadOptions = builder.directedReadOptions;
useVirtualThreads = builder.useVirtualThreads;
+ openTelemetry = builder.openTelemetry;
}
/**
@@ -737,6 +755,7 @@ public static class Builder
private boolean attemptDirectPath = true;
private DirectedReadOptions directedReadOptions;
private boolean useVirtualThreads = false;
+ private OpenTelemetry openTelemetry;
private static String createCustomClientLibToken(String token) {
return token + " " + ServiceOptions.getGoogApiClientLibName();
@@ -1243,6 +1262,15 @@ public Builder setEmulatorHost(String emulatorHost) {
return this;
}
+ /**
+ * Sets OpenTelemetry object to be used for Spanner Metrics and Traces. GlobalOpenTelemetry will
+ * be used as fallback if this options is not set.
+ */
+ public Builder setOpenTelemetry(OpenTelemetry openTelemetry) {
+ this.openTelemetry = openTelemetry;
+ return this;
+ }
+
/**
* Enable leader aware routing. Leader aware routing would route all requests in RW/PDML
* transactions to the leader region.
@@ -1297,6 +1325,11 @@ public SpannerOptions build() {
this.grpcGcpExtensionEnabled ? GRPC_GCP_ENABLED_DEFAULT_CHANNELS : DEFAULT_CHANNELS;
}
+ synchronized (lock) {
+ if (activeTracingFramework == null) {
+ activeTracingFramework = TracingFramework.OPEN_CENSUS;
+ }
+ }
return new SpannerOptions(this);
}
}
@@ -1326,6 +1359,77 @@ public static void useDefaultEnvironment() {
SpannerOptions.environment = SpannerEnvironmentImpl.INSTANCE;
}
+ /**
+ * Enables OpenTelemetry traces. Enabling OpenTelemetry traces will disable OpenCensus traces. By
+ * default, OpenCensus traces are enabled.
+ */
+ public static void enableOpenTelemetryTraces() {
+ synchronized (lock) {
+ if (activeTracingFramework != null
+ && activeTracingFramework != TracingFramework.OPEN_TELEMETRY) {
+ throw new IllegalStateException(
+ "ActiveTracingFramework is set to OpenCensus and cannot be reset after SpannerOptions object is created.");
+ }
+ activeTracingFramework = TracingFramework.OPEN_TELEMETRY;
+ }
+ }
+
+ /** Enables OpenCensus traces. Enabling OpenCensus traces will disable OpenTelemetry traces. */
+ @ObsoleteApi(
+ "The OpenCensus project is deprecated. Use enableOpenTelemetryTraces to switch to OpenTelemetry traces")
+ public static void enableOpenCensusTraces() {
+ synchronized (lock) {
+ if (activeTracingFramework != null
+ && activeTracingFramework != TracingFramework.OPEN_CENSUS) {
+ throw new IllegalStateException(
+ "ActiveTracingFramework is set to OpenTelemetry and cannot be reset after SpannerOptions object is created.");
+ }
+ activeTracingFramework = TracingFramework.OPEN_CENSUS;
+ }
+ }
+
+ /**
+ * Always resets the activeTracingFramework. This variable is used for internal testing, and is
+ * not a valid production scenario
+ */
+ @ObsoleteApi(
+ "The OpenCensus project is deprecated. Use enableOpenTelemetryTraces to switch to OpenTelemetry traces")
+ static void resetActiveTracingFramework() {
+ activeTracingFramework = null;
+ }
+
+ public static TracingFramework getActiveTracingFramework() {
+ synchronized (lock) {
+ if (activeTracingFramework == null) {
+ return TracingFramework.OPEN_CENSUS;
+ }
+ return activeTracingFramework;
+ }
+ }
+
+ /** Disables OpenCensus metrics. Disable OpenCensus metrics before creating Spanner client. */
+ public static void disableOpenCensusMetrics() {
+ SpannerOptions.enableOpenCensusMetrics = false;
+ }
+
+ @VisibleForTesting
+ static void enableOpenCensusMetrics() {
+ SpannerOptions.enableOpenCensusMetrics = true;
+ }
+
+ public static boolean isEnabledOpenCensusMetrics() {
+ return SpannerOptions.enableOpenCensusMetrics;
+ }
+
+ /** Enables OpenTelemetry metrics. Enable OpenTelemetry metrics before creating Spanner client. */
+ public static void enableOpenTelemetryMetrics() {
+ SpannerOptions.enableOpenTelemetryMetrics = true;
+ }
+
+ public static boolean isEnabledOpenTelemetryMetrics() {
+ return SpannerOptions.enableOpenTelemetryMetrics;
+ }
+
@Override
protected String getDefaultProject() {
String projectId = getDefaultProjectId();
@@ -1426,6 +1530,18 @@ public boolean isAttemptDirectPath() {
return attemptDirectPath;
}
+ /**
+ * Returns an instance of OpenTelemetry. If OpenTelemetry object is not set via SpannerOptions
+ * then GlobalOpenTelemetry will be used as fallback.
+ */
+ public OpenTelemetry getOpenTelemetry() {
+ if (this.openTelemetry != null) {
+ return this.openTelemetry;
+ } else {
+ return GlobalOpenTelemetry.get();
+ }
+ }
+
@BetaApi
public boolean isUseVirtualThreads() {
return useVirtualThreads;
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerRpcMetrics.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerRpcMetrics.java
new file mode 100644
index 00000000000..794c211971d
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerRpcMetrics.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 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.
+ */
+package com.google.cloud.spanner;
+
+import com.google.api.core.InternalApi;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.Meter;
+import java.util.Arrays;
+import java.util.List;
+
+@InternalApi
+public class SpannerRpcMetrics {
+ private final LongHistogram gfeLatencies;
+ private final LongCounter gfeHeaderMissingCount;
+
+ public SpannerRpcMetrics(OpenTelemetry openTelemetry) {
+ if (!SpannerOptions.isEnabledOpenTelemetryMetrics()) {
+ gfeLatencies = null;
+ gfeHeaderMissingCount = null;
+ return;
+ }
+
+ Meter meter = openTelemetry.getMeter(MetricRegistryConstants.INSTRUMENTATION_SCOPE);
+ List RPC_MILLIS_BUCKET_BOUNDARIES =
+ Arrays.asList(
+ 1L, 2L, 3L, 4L, 5L, 6L, 8L, 10L, 13L, 16L, 20L, 25L, 30L, 40L, 50L, 65L, 80L, 100L,
+ 130L, 160L, 200L, 250L, 300L, 400L, 500L, 650L, 800L, 1000L, 2000L, 5000L, 10000L,
+ 20000L, 50000L, 100000L);
+ gfeLatencies =
+ meter
+ .histogramBuilder(MetricRegistryConstants.SPANNER_GFE_LATENCY)
+ .ofLongs()
+ .setDescription(MetricRegistryConstants.SPANNER_GFE_LATENCY_DESCRIPTION)
+ .setUnit("ms")
+ .setExplicitBucketBoundariesAdvice(RPC_MILLIS_BUCKET_BOUNDARIES)
+ .build();
+ gfeHeaderMissingCount =
+ meter
+ .counterBuilder(MetricRegistryConstants.SPANNER_GFE_HEADER_MISSING_COUNT)
+ .setDescription(MetricRegistryConstants.SPANNER_GFE_HEADER_MISSING_COUNT_DESCRIPTION)
+ .setUnit(MetricRegistryConstants.COUNT)
+ .build();
+ }
+
+ @InternalApi
+ public void recordGfeLatency(long value, Attributes attributes) {
+ if (gfeLatencies != null) {
+ gfeLatencies.record(value, attributes);
+ }
+ }
+
+ @InternalApi
+ public void recordGfeHeaderMissingCount(long value, Attributes attributes) {
+ if (gfeHeaderMissingCount != null) {
+ gfeHeaderMissingCount.add(value, attributes);
+ }
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ThreadFactoryUtil.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ThreadFactoryUtil.java
index 67f4d3230d4..72d58e85be3 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ThreadFactoryUtil.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ThreadFactoryUtil.java
@@ -69,6 +69,11 @@ public static ThreadFactory tryCreateVirtualThreadFactory(String baseNameFormat)
} catch (ClassNotFoundException | NoSuchMethodException ignore) {
return null;
} catch (InvocationTargetException | IllegalAccessException e) {
+ // Java 20 supports virtual threads as an experimental feature. It will throw an
+ // UnsupportedOperationException if experimental features have not been enabled.
+ if (e.getCause() instanceof UnsupportedOperationException) {
+ return null;
+ }
throw new RuntimeException(e);
}
}
@@ -91,6 +96,11 @@ public static ExecutorService tryCreateVirtualThreadPerTaskExecutor(String baseN
} catch (NoSuchMethodException ignore) {
return null;
} catch (InvocationTargetException | IllegalAccessException e) {
+ // Java 20 supports virtual threads as an experimental feature. It will throw an
+ // UnsupportedOperationException if experimental features have not been enabled.
+ if (e.getCause() instanceof UnsupportedOperationException) {
+ return null;
+ }
throw new RuntimeException(e);
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TraceUtil.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TraceUtil.java
deleted file mode 100644
index 0d429661ad2..00000000000
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TraceUtil.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2017 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
- *
- * http://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.
- */
-
-package com.google.cloud.spanner;
-
-import com.google.cloud.Timestamp;
-import com.google.common.collect.ImmutableMap;
-import com.google.spanner.v1.Transaction;
-import io.opencensus.contrib.grpc.util.StatusConverter;
-import io.opencensus.trace.AttributeValue;
-import io.opencensus.trace.EndSpanOptions;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Status;
-import java.util.Map;
-
-/** Utility methods for tracing. */
-class TraceUtil {
-
- static final EndSpanOptions END_SPAN_OPTIONS =
- EndSpanOptions.builder().setSampleToLocalSpanStore(true).build();
-
- static Map getTransactionAnnotations(Transaction t) {
- return ImmutableMap.of(
- "Id",
- AttributeValue.stringAttributeValue(t.getId().toStringUtf8()),
- "Timestamp",
- AttributeValue.stringAttributeValue(Timestamp.fromProto(t.getReadTimestamp()).toString()));
- }
-
- static ImmutableMap getExceptionAnnotations(Throwable e) {
- if (e instanceof SpannerException) {
- return ImmutableMap.of(
- "Status",
- AttributeValue.stringAttributeValue(((SpannerException) e).getErrorCode().toString()));
- }
- return ImmutableMap.of();
- }
-
- static ImmutableMap getExceptionAnnotations(SpannerException e) {
- return ImmutableMap.of(
- "Status", AttributeValue.stringAttributeValue(e.getErrorCode().toString()));
- }
-
- static void setWithFailure(Span span, Throwable e) {
- if (e instanceof SpannerException) {
- span.setStatus(
- StatusConverter.fromGrpcStatus(((SpannerException) e).getErrorCode().getGrpcStatus())
- .withDescription(e.getMessage()));
- } else {
- span.setStatus(Status.INTERNAL.withDescription(e.getMessage()));
- }
- }
-
- static void endSpanWithFailure(Span span, Throwable e) {
- if (e instanceof SpannerException) {
- endSpanWithFailure(span, (SpannerException) e);
- } else {
- span.setStatus(Status.INTERNAL.withDescription(e.getMessage()));
- span.end(END_SPAN_OPTIONS);
- }
- }
-
- static void endSpanWithFailure(Span span, SpannerException e) {
- span.setStatus(
- StatusConverter.fromGrpcStatus(e.getErrorCode().getGrpcStatus())
- .withDescription(e.getMessage()));
- span.end(END_SPAN_OPTIONS);
- }
-}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TraceWrapper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TraceWrapper.java
new file mode 100644
index 00000000000..25796968e9e
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TraceWrapper.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner;
+
+import com.google.cloud.spanner.SpannerOptions.TracingFramework;
+import io.opencensus.trace.BlankSpan;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.Tracer;
+import io.opentelemetry.context.Context;
+
+class TraceWrapper {
+
+ private final Tracer openCensusTracer;
+ private final io.opentelemetry.api.trace.Tracer openTelemetryTracer;
+
+ TraceWrapper(Tracer openCensusTracer, io.opentelemetry.api.trace.Tracer openTelemetryTracer) {
+ this.openTelemetryTracer = openTelemetryTracer;
+ this.openCensusTracer = openCensusTracer;
+ }
+
+ ISpan spanBuilder(String spanName) {
+ if (SpannerOptions.getActiveTracingFramework().equals(TracingFramework.OPEN_TELEMETRY)) {
+ return new OpenTelemetrySpan(openTelemetryTracer.spanBuilder(spanName).startSpan());
+ } else {
+ return new OpenCensusSpan(openCensusTracer.spanBuilder(spanName).startSpan());
+ }
+ }
+
+ ISpan spanBuilderWithExplicitParent(String spanName, ISpan parentSpan) {
+ if (SpannerOptions.getActiveTracingFramework().equals(TracingFramework.OPEN_TELEMETRY)) {
+ OpenTelemetrySpan otParentSpan = (OpenTelemetrySpan) parentSpan;
+
+ io.opentelemetry.api.trace.Span otSpan;
+
+ if (otParentSpan != null && otParentSpan.getOpenTelemetrySpan() != null) {
+ otSpan =
+ openTelemetryTracer
+ .spanBuilder(spanName)
+ .setParent(Context.current().with(otParentSpan.getOpenTelemetrySpan()))
+ .startSpan();
+ } else {
+ otSpan = openTelemetryTracer.spanBuilder(spanName).startSpan();
+ }
+
+ return new OpenTelemetrySpan(otSpan);
+
+ } else {
+ OpenCensusSpan parentOcSpan = (OpenCensusSpan) parentSpan;
+ Span ocSpan =
+ openCensusTracer
+ .spanBuilderWithExplicitParent(
+ spanName, parentOcSpan != null ? parentOcSpan.getOpenCensusSpan() : null)
+ .startSpan();
+
+ return new OpenCensusSpan(ocSpan);
+ }
+ }
+
+ ISpan getCurrentSpan() {
+ if (SpannerOptions.getActiveTracingFramework().equals(TracingFramework.OPEN_TELEMETRY)) {
+ return new OpenTelemetrySpan(
+ io.opentelemetry.api.trace.Span.fromContext(io.opentelemetry.context.Context.current()));
+ } else {
+ return new OpenCensusSpan(openCensusTracer.getCurrentSpan());
+ }
+ }
+
+ ISpan getBlankSpan() {
+ if (SpannerOptions.getActiveTracingFramework().equals(TracingFramework.OPEN_TELEMETRY)) {
+ return new OpenTelemetrySpan(io.opentelemetry.api.trace.Span.getInvalid());
+ } else {
+ return new OpenCensusSpan(BlankSpan.INSTANCE);
+ }
+ }
+
+ IScope withSpan(ISpan span) {
+ if (SpannerOptions.getActiveTracingFramework().equals(TracingFramework.OPEN_TELEMETRY)) {
+ OpenTelemetrySpan openTelemetrySpan;
+ if (!(span instanceof OpenTelemetrySpan)) {
+ openTelemetrySpan = new OpenTelemetrySpan(null);
+ } else {
+ openTelemetrySpan = (OpenTelemetrySpan) span;
+ }
+ return new OpenTelemetryScope(openTelemetrySpan.getOpenTelemetrySpan().makeCurrent());
+ } else {
+ OpenCensusSpan openCensusSpan;
+ if (!(span instanceof OpenCensusSpan)) {
+ openCensusSpan = new OpenCensusSpan(null);
+ } else {
+ openCensusSpan = (OpenCensusSpan) span;
+ }
+ return new OpenCensusScope(openCensusTracer.withSpan(openCensusSpan.getOpenCensusSpan()));
+ }
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManagerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManagerImpl.java
index 2d47fcd5c78..95ffd1168b2 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManagerImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionManagerImpl.java
@@ -20,41 +20,39 @@
import com.google.cloud.spanner.Options.TransactionOption;
import com.google.cloud.spanner.SessionImpl.SessionTransaction;
import com.google.common.base.Preconditions;
-import io.opencensus.common.Scope;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Tracer;
-import io.opencensus.trace.Tracing;
/** Implementation of {@link TransactionManager}. */
final class TransactionManagerImpl implements TransactionManager, SessionTransaction {
- private static final Tracer tracer = Tracing.getTracer();
+ private final TraceWrapper tracer;
private final SessionImpl session;
- private Span span;
+ private ISpan span;
private final Options options;
private TransactionRunnerImpl.TransactionContextImpl txn;
private TransactionState txnState;
- TransactionManagerImpl(SessionImpl session, Span span, TransactionOption... options) {
+ TransactionManagerImpl(
+ SessionImpl session, ISpan span, TraceWrapper tracer, TransactionOption... options) {
this.session = session;
this.span = span;
+ this.tracer = tracer;
this.options = Options.fromTransactionOptions(options);
}
- Span getSpan() {
+ ISpan getSpan() {
return span;
}
@Override
- public void setSpan(Span span) {
+ public void setSpan(ISpan span) {
this.span = span;
}
@Override
public TransactionContext begin() {
Preconditions.checkState(txn == null, "begin can only be called once");
- try (Scope s = tracer.withSpan(span)) {
+ try (IScope s = tracer.withSpan(span)) {
txn = session.newTransaction(options);
session.setActive(this);
txnState = TransactionState.STARTED;
@@ -102,7 +100,7 @@ public TransactionContext resetForRetry() {
throw new IllegalStateException(
"resetForRetry can only be called if the previous attempt" + " aborted");
}
- try (Scope s = tracer.withSpan(span)) {
+ try (IScope s = tracer.withSpan(span)) {
boolean useInlinedBegin = txn.transactionId != null;
txn = session.newTransaction(options);
if (!useInlinedBegin) {
@@ -137,7 +135,7 @@ public void close() {
txnState = TransactionState.ROLLED_BACK;
}
} finally {
- span.end(TraceUtil.END_SPAN_OPTIONS);
+ span.end();
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
index 21c74a400f0..3249be1bdb3 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
@@ -49,11 +49,6 @@
import com.google.spanner.v1.Transaction;
import com.google.spanner.v1.TransactionOptions;
import com.google.spanner.v1.TransactionSelector;
-import io.opencensus.common.Scope;
-import io.opencensus.trace.AttributeValue;
-import io.opencensus.trace.Span;
-import io.opencensus.trace.Tracer;
-import io.opencensus.trace.Tracing;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
@@ -71,7 +66,6 @@
/** Default implementation of {@link TransactionRunner}. */
class TransactionRunnerImpl implements SessionTransaction, TransactionRunner {
- private static final Tracer tracer = Tracing.getTracer();
private static final Logger txnLogger = Logger.getLogger(TransactionRunner.class.getName());
/**
* (Part of) the error message that is returned by Cloud Spanner if a transaction is cancelled
@@ -84,6 +78,7 @@ class TransactionRunnerImpl implements SessionTransaction, TransactionRunner {
@VisibleForTesting
static class TransactionContextImpl extends AbstractReadContext implements TransactionContext {
+
static class Builder extends AbstractReadContext.Builder {
private Clock clock = new Clock();
@@ -131,6 +126,7 @@ static Builder newBuilder() {
*/
private class TransactionContextAsyncResultSetImpl extends ForwardingAsyncResultSet
implements ListenableAsyncResultSet {
+
private TransactionContextAsyncResultSetImpl(ListenableAsyncResultSet delegate) {
super(delegate);
}
@@ -254,10 +250,7 @@ ApiFuture ensureTxnAsync() {
if (transactionId == null || isAborted()) {
createTxnAsync(res);
} else {
- span.addAnnotation(
- "Transaction Initialized",
- ImmutableMap.of(
- "Id", AttributeValue.stringAttributeValue(transactionId.toStringUtf8())));
+ span.addAnnotation("Transaction Initialized", "Id", transactionId.toStringUtf8());
txnLogger.log(
Level.FINER,
"Using prepared transaction {0}",
@@ -274,10 +267,7 @@ private void createTxnAsync(final SettableApiFuture res) {
() -> {
try {
transactionId = fut.get();
- span.addAnnotation(
- "Transaction Creation Done",
- ImmutableMap.of(
- "Id", AttributeValue.stringAttributeValue(transactionId.toStringUtf8())));
+ span.addAnnotation("Transaction Creation Done", "Id", transactionId.toStringUtf8());
txnLogger.log(
Level.FINER,
"Started transaction {0}",
@@ -285,8 +275,7 @@ private void createTxnAsync(final SettableApiFuture res) {
res.set(null);
} catch (ExecutionException e) {
span.addAnnotation(
- "Transaction Creation Failed",
- TraceUtil.getExceptionAnnotations(e.getCause() == null ? e : e.getCause()));
+ "Transaction Creation Failed", e.getCause() == null ? e : e.getCause());
res.setException(e.getCause() == null ? e : e.getCause());
} catch (InterruptedException e) {
res.setException(SpannerExceptionFactory.propagateInterrupt(e));
@@ -339,6 +328,13 @@ ApiFuture commitAsync() {
}
builder.setRequestOptions(requestOptionsBuilder.build());
}
+ if (options.hasMaxCommitDelay()) {
+ builder.setMaxCommitDelay(
+ com.google.protobuf.Duration.newBuilder()
+ .setSeconds(options.maxCommitDelay().getSeconds())
+ .setNanos(options.maxCommitDelay().getNano())
+ .build());
+ }
synchronized (lock) {
if (transactionIdFuture == null && transactionId == null && runningAsyncOperations == 0) {
finishOps = SettableApiFuture.create();
@@ -354,6 +350,7 @@ ApiFuture commitAsync() {
}
private final class CommitRunnable implements Runnable {
+
private final SettableApiFuture res;
private final ApiFuture prev;
private final CommitRequest.Builder requestBuilder;
@@ -394,39 +391,37 @@ public void run() {
}
final CommitRequest commitRequest = requestBuilder.build();
span.addAnnotation("Starting Commit");
- final Span opSpan =
- tracer.spanBuilderWithExplicitParent(SpannerImpl.COMMIT, span).startSpan();
+ final ISpan opSpan = tracer.spanBuilderWithExplicitParent(SpannerImpl.COMMIT, span);
final ApiFuture commitFuture =
rpc.commitAsync(commitRequest, session.getOptions());
session.markUsed(clock.instant());
commitFuture.addListener(
- tracer.withSpan(
- opSpan,
- () -> {
- try {
- com.google.spanner.v1.CommitResponse proto = commitFuture.get();
- if (!proto.hasCommitTimestamp()) {
- throw newSpannerException(
- ErrorCode.INTERNAL, "Missing commitTimestamp:\n" + session.getName());
- }
- span.addAnnotation("Commit Done");
- opSpan.end(TraceUtil.END_SPAN_OPTIONS);
- res.set(new CommitResponse(proto));
- } catch (Throwable e) {
- if (e instanceof ExecutionException) {
- e =
- SpannerExceptionFactory.newSpannerException(
- e.getCause() == null ? e : e.getCause());
- } else if (e instanceof InterruptedException) {
- e = SpannerExceptionFactory.propagateInterrupt((InterruptedException) e);
- } else {
- e = SpannerExceptionFactory.newSpannerException(e);
- }
- span.addAnnotation("Commit Failed", TraceUtil.getExceptionAnnotations(e));
- TraceUtil.endSpanWithFailure(opSpan, e);
- res.setException(onError((SpannerException) e, false));
- }
- }),
+ () -> {
+ try (IScope s = tracer.withSpan(opSpan)) {
+ com.google.spanner.v1.CommitResponse proto = commitFuture.get();
+ if (!proto.hasCommitTimestamp()) {
+ throw newSpannerException(
+ ErrorCode.INTERNAL, "Missing commitTimestamp:\n" + session.getName());
+ }
+ span.addAnnotation("Commit Done");
+ opSpan.end();
+ res.set(new CommitResponse(proto));
+ } catch (Throwable e) {
+ if (e instanceof ExecutionException) {
+ e =
+ SpannerExceptionFactory.newSpannerException(
+ e.getCause() == null ? e : e.getCause());
+ } else if (e instanceof InterruptedException) {
+ e = SpannerExceptionFactory.propagateInterrupt((InterruptedException) e);
+ } else {
+ e = SpannerExceptionFactory.newSpannerException(e);
+ }
+ span.addAnnotation("Commit Failed", e);
+ opSpan.setStatus(e);
+ opSpan.end();
+ res.setException(onError((SpannerException) e, false));
+ }
+ },
MoreExecutors.directExecutor());
} catch (InterruptedException e) {
res.setException(SpannerExceptionFactory.propagateInterrupt(e));
@@ -435,6 +430,9 @@ public void run() {
} catch (ExecutionException e) {
res.setException(
SpannerExceptionFactory.newSpannerException(e.getCause() == null ? e : e.getCause()));
+ } catch (Throwable e) {
+ res.setException(
+ SpannerExceptionFactory.newSpannerException(e.getCause() == null ? e : e.getCause()));
}
}
}
@@ -455,7 +453,7 @@ void rollback() {
rollbackAsync().get();
} catch (ExecutionException e) {
txnLogger.log(Level.FINE, "Exception during rollback", e);
- span.addAnnotation("Rollback Failed", TraceUtil.getExceptionAnnotations(e));
+ span.addAnnotation("Rollback Failed", e);
} catch (InterruptedException e) {
throw SpannerExceptionFactory.propagateInterrupt(e);
}
@@ -575,7 +573,9 @@ public void onTransactionMetadata(Transaction transaction, boolean shouldInclude
@Nullable
String getTransactionTag() {
- if (this.options.hasTag()) return this.options.tag();
+ if (this.options.hasTag()) {
+ return this.options.tag();
+ }
return null;
}
@@ -949,7 +949,8 @@ public ListenableAsyncResultSet executeQueryAsync(
private boolean blockNestedTxn = true;
private final SessionImpl session;
private final Options options;
- private Span span;
+ private ISpan span;
+ private TraceWrapper tracer;
private TransactionContextImpl txn;
private volatile boolean isValid = true;
@@ -963,29 +964,31 @@ public TransactionRunner allowNestedTransaction() {
this.session = session;
this.options = Options.fromTransactionOptions(options);
this.txn = session.newTransaction(this.options);
+ this.tracer = session.getTracer();
}
@Override
- public void setSpan(Span span) {
+ public void setSpan(ISpan span) {
this.span = span;
}
@Nullable
@Override
public T run(TransactionCallable callable) {
- try (Scope s = tracer.withSpan(span)) {
+ try (IScope s = tracer.withSpan(span)) {
if (blockNestedTxn) {
SessionImpl.hasPendingTransaction.set(Boolean.TRUE);
}
return runInternal(callable);
} catch (RuntimeException e) {
- TraceUtil.setWithFailure(span, e);
+ span.setStatus(e);
throw e;
} finally {
// Remove threadLocal rather than set to FALSE to avoid a possible memory leak.
// We also do this unconditionally in case a user has modified the flag when the transaction
// was running.
SessionImpl.hasPendingTransaction.remove();
+ span.end();
}
}
@@ -1003,9 +1006,7 @@ private T runInternal(final TransactionCallable txCallable) {
checkState(
isValid, "TransactionRunner has been invalidated by a new operation on the session");
attempt.incrementAndGet();
- span.addAnnotation(
- "Starting Transaction Attempt",
- ImmutableMap.of("Attempt", AttributeValue.longAttributeValue(attempt.longValue())));
+ span.addAnnotation("Starting Transaction Attempt", "Attempt", attempt.longValue());
// Only ensure that there is a transaction if we should not inline the beginTransaction
// with the first statement.
if (!useInlinedBegin) {
@@ -1022,8 +1023,8 @@ private T runInternal(final TransactionCallable txCallable) {
if (txn.isAborted() || (e instanceof AbortedException)) {
span.addAnnotation(
"Transaction Attempt Aborted in user operation. Retrying",
- ImmutableMap.of(
- "Attempt", AttributeValue.longAttributeValue(attempt.longValue())));
+ "Attempt",
+ attempt.longValue());
shouldRollback = false;
if (e instanceof AbortedException) {
throw e;
@@ -1039,10 +1040,8 @@ private T runInternal(final TransactionCallable txCallable) {
}
span.addAnnotation(
"Transaction Attempt Failed in user operation",
- ImmutableMap.builder()
- .putAll(TraceUtil.getExceptionAnnotations(toThrow))
- .put("Attempt", AttributeValue.longAttributeValue(attempt.longValue()))
- .build());
+ ImmutableMap.of(
+ "Attempt", attempt.longValue(), "Status", toThrow.getErrorCode().toString()));
throw toThrow;
} finally {
if (shouldRollback) {
@@ -1052,23 +1051,18 @@ private T runInternal(final TransactionCallable txCallable) {
try {
txn.commit();
- span.addAnnotation(
- "Transaction Attempt Succeeded",
- ImmutableMap.of("Attempt", AttributeValue.longAttributeValue(attempt.longValue())));
+ span.addAnnotation("Transaction Attempt Succeeded", "Attempt", attempt.longValue());
return result;
} catch (AbortedException e) {
txnLogger.log(Level.FINE, "Commit aborted", e);
span.addAnnotation(
- "Transaction Attempt Aborted in Commit. Retrying",
- ImmutableMap.of("Attempt", AttributeValue.longAttributeValue(attempt.longValue())));
+ "Transaction Attempt Aborted in Commit. Retrying", "Attempt", attempt.longValue());
throw e;
} catch (SpannerException e) {
span.addAnnotation(
"Transaction Attempt Failed in Commit",
- ImmutableMap.builder()
- .putAll(TraceUtil.getExceptionAnnotations(e))
- .put("Attempt", AttributeValue.longAttributeValue(attempt.longValue()))
- .build());
+ ImmutableMap.of(
+ "Attempt", attempt.longValue(), "Status", e.getErrorCode().toString()));
throw e;
}
};
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java
index 629ab9cca6a..8fc3043791e 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java
@@ -470,7 +470,7 @@ ParsedStatement parse(Statement statement, QueryOptions defaultQueryOptions) {
ParsedStatement parsedStatement = statementCache.getIfPresent(statement.getSql());
if (parsedStatement == null) {
- parsedStatement = internalParse(statement, null);
+ parsedStatement = internalParse(statement, defaultQueryOptions);
statementCache.put(statement.getSql(), parsedStatement.forCache());
return parsedStatement;
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
index a477a664ec6..6be277777a6 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java
@@ -18,6 +18,7 @@
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.Options.RpcPriority;
+import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.TimestampBound.Mode;
@@ -27,6 +28,7 @@
import com.google.common.base.Preconditions;
import com.google.protobuf.Duration;
import com.google.protobuf.util.Durations;
+import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.RequestOptions.Priority;
import java.util.EnumSet;
import java.util.HashMap;
@@ -306,6 +308,48 @@ public TimestampBound convert(String value) {
return null;
}
}
+ /**
+ * Converter from string to possible values for {@link com.google.spanner.v1.DirectedReadOptions}.
+ */
+ static class DirectedReadOptionsConverter
+ implements ClientSideStatementValueConverter {
+ private final Pattern allowedValues;
+
+ public DirectedReadOptionsConverter(String allowedValues) {
+ // Remove the single quotes at the beginning and end.
+ this.allowedValues =
+ Pattern.compile(
+ "(?is)\\A" + allowedValues.substring(1, allowedValues.length() - 1) + "\\z");
+ }
+
+ @Override
+ public Class getParameterClass() {
+ return DirectedReadOptions.class;
+ }
+
+ @Override
+ public DirectedReadOptions convert(String value) {
+ Matcher matcher = allowedValues.matcher(value);
+ if (matcher.find()) {
+ try {
+ return DirectedReadOptionsUtil.parse(value);
+ } catch (SpannerException spannerException) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ String.format(
+ "Failed to parse '%s' as a valid value for DIRECTED_READ.\n"
+ + "The value should be a JSON string like this: '%s'.\n"
+ + "You can generate a valid JSON string from a DirectedReadOptions instance by calling %s.%s",
+ value,
+ "{\"includeReplicas\":{\"replicaSelections\":[{\"location\":\"eu-west1\",\"type\":\"READ_ONLY\"}]}}",
+ DirectedReadOptionsUtil.class.getName(),
+ "toString(DirectedReadOptions directedReadOptions)"),
+ spannerException);
+ }
+ }
+ return null;
+ }
+ }
/** Converter for converting strings to {@link AutocommitDmlMode} values. */
static class AutocommitDmlModeConverter
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
index e58610b7d03..ffcef91d9c9 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
@@ -38,6 +38,7 @@
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.connection.StatementResult.ResultType;
+import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.ExecuteBatchDmlRequest;
import com.google.spanner.v1.ResultSetStats;
import java.util.Iterator;
@@ -489,6 +490,22 @@ default String getStatementTag() {
*/
TimestampBound getReadOnlyStaleness();
+ /**
+ * Sets the {@link DirectedReadOptions} to use for both single-use and multi-use read-only
+ * transactions on this connection.
+ */
+ default void setDirectedRead(DirectedReadOptions directedReadOptions) {
+ throw new UnsupportedOperationException("Unimplemented");
+ }
+
+ /**
+ * Returns the {@link DirectedReadOptions} that are used for both single-use and multi-use
+ * read-only transactions on this connection.
+ */
+ default DirectedReadOptions getDirectedRead() {
+ throw new UnsupportedOperationException("Unimplemented");
+ }
+
/**
* Sets the query optimizer version to use for this connection.
*
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
index 3e524e4fe92..e9aed66b6bb 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
@@ -53,6 +53,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
import com.google.spanner.v1.ResultSetStats;
import java.util.ArrayList;
@@ -236,6 +237,7 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
*/
private int maxPartitionedParallelism;
+ private DirectedReadOptions directedReadOptions = null;
private QueryOptions queryOptions = QueryOptions.getDefaultInstance();
private RpcPriority rpcPriority = null;
private SavepointSupport savepointSupport = SavepointSupport.FAIL_AFTER_ROLLBACK;
@@ -510,6 +512,21 @@ public TimestampBound getReadOnlyStaleness() {
return this.readOnlyStaleness;
}
+ @Override
+ public void setDirectedRead(DirectedReadOptions directedReadOptions) {
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ ConnectionPreconditions.checkState(
+ !isTransactionStarted(),
+ "Cannot set directed read options when a transaction has been started");
+ this.directedReadOptions = directedReadOptions;
+ }
+
+ @Override
+ public DirectedReadOptions getDirectedRead() {
+ ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
+ return this.directedReadOptions;
+ }
+
@Override
public void setOptimizerVersion(String optimizerVersion) {
Preconditions.checkNotNull(optimizerVersion);
@@ -1131,7 +1148,8 @@ public ResultSet partitionQuery(
CallType.SYNC,
parsedStatement,
getEffectivePartitionOptions(partitionOptions),
- mergeDataBoost(mergeQueryRequestOptions(mergeQueryStatementTag(options)))));
+ mergeDataBoost(
+ mergeQueryRequestOptions(parsedStatement, mergeQueryStatementTag(options)))));
}
private PartitionOptions getEffectivePartitionOptions(
@@ -1427,41 +1445,38 @@ private List parseUpdateStatements(Iterable updates)
private QueryOption[] mergeDataBoost(QueryOption... options) {
if (this.dataBoostEnabled) {
-
- // Shortcut for the most common scenario.
- if (options == null || options.length == 0) {
- options = new QueryOption[] {Options.dataBoostEnabled(true)};
- } else {
- options = Arrays.copyOf(options, options.length + 1);
- options[options.length - 1] = Options.dataBoostEnabled(true);
- }
+ options = appendQueryOption(options, Options.dataBoostEnabled(true));
}
return options;
}
private QueryOption[] mergeQueryStatementTag(QueryOption... options) {
if (this.statementTag != null) {
- // Shortcut for the most common scenario.
- if (options == null || options.length == 0) {
- options = new QueryOption[] {Options.tag(statementTag)};
- } else {
- options = Arrays.copyOf(options, options.length + 1);
- options[options.length - 1] = Options.tag(statementTag);
- }
+ options = appendQueryOption(options, Options.tag(statementTag));
this.statementTag = null;
}
return options;
}
- private QueryOption[] mergeQueryRequestOptions(QueryOption... options) {
+ private QueryOption[] mergeQueryRequestOptions(
+ ParsedStatement parsedStatement, QueryOption... options) {
if (this.rpcPriority != null) {
- // Shortcut for the most common scenario.
- if (options == null || options.length == 0) {
- options = new QueryOption[] {Options.priority(this.rpcPriority)};
- } else {
- options = Arrays.copyOf(options, options.length + 1);
- options[options.length - 1] = Options.priority(this.rpcPriority);
- }
+ options = appendQueryOption(options, Options.priority(this.rpcPriority));
+ }
+ if (this.directedReadOptions != null
+ && currentUnitOfWork != null
+ && currentUnitOfWork.supportsDirectedReads(parsedStatement)) {
+ options = appendQueryOption(options, Options.directedRead(this.directedReadOptions));
+ }
+ return options;
+ }
+
+ private QueryOption[] appendQueryOption(QueryOption[] options, QueryOption append) {
+ if (options == null || options.length == 0) {
+ options = new QueryOption[] {append};
+ } else {
+ options = Arrays.copyOf(options, options.length + 1);
+ options[options.length - 1] = append;
}
return options;
}
@@ -1516,7 +1531,7 @@ private ResultSet internalExecuteQuery(
callType,
statement,
analyzeMode,
- mergeQueryRequestOptions(mergeQueryStatementTag(options))));
+ mergeQueryRequestOptions(statement, mergeQueryStatementTag(options))));
}
private AsyncResultSet internalExecuteQueryAsync(
@@ -1538,7 +1553,7 @@ private AsyncResultSet internalExecuteQueryAsync(
callType,
statement,
analyzeMode,
- mergeQueryRequestOptions(mergeQueryStatementTag(options))),
+ mergeQueryRequestOptions(statement, mergeQueryStatementTag(options))),
spanner.getAsyncExecutorProvider(),
options);
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
index eb317790976..3fbc3e7a8d4 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java
@@ -20,6 +20,7 @@
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.connection.PgTransactionMode.IsolationLevel;
import com.google.protobuf.Duration;
+import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.RequestOptions.Priority;
/**
@@ -65,6 +66,10 @@ interface ConnectionStatementExecutor {
StatementResult statementShowReadOnlyStaleness();
+ StatementResult statementSetDirectedRead(DirectedReadOptions directedReadOptions);
+
+ StatementResult statementShowDirectedRead();
+
StatementResult statementSetOptimizerVersion(String optimizerVersion);
StatementResult statementShowOptimizerVersion();
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
index a3b4f92f145..0e8bdada223 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java
@@ -28,6 +28,7 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DATA_BOOST_ENABLED;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DEFAULT_TRANSACTION_ISOLATION;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
+import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_DIRECTED_READ;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONED_PARALLELISM;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_MAX_PARTITIONS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_OPTIMIZER_STATISTICS_PACKAGE;
@@ -49,6 +50,7 @@
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_COMMIT_TIMESTAMP;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DATA_BOOST_ENABLED;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE;
+import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_DIRECTED_READ;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONED_PARALLELISM;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_MAX_PARTITIONS;
import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_STATISTICS_PACKAGE;
@@ -91,6 +93,7 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.Duration;
+import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.PlanNode;
import com.google.spanner.v1.QueryPlan;
import com.google.spanner.v1.RequestOptions;
@@ -283,6 +286,21 @@ public StatementResult statementShowReadOnlyStaleness() {
SHOW_READ_ONLY_STALENESS);
}
+ @Override
+ public StatementResult statementSetDirectedRead(DirectedReadOptions directedReadOptions) {
+ getConnection().setDirectedRead(directedReadOptions);
+ return noResult(SET_DIRECTED_READ);
+ }
+
+ @Override
+ public StatementResult statementShowDirectedRead() {
+ DirectedReadOptions directedReadOptions = getConnection().getDirectedRead();
+ return resultSet(
+ String.format("%sDIRECTED_READ", getNamespace(connection.getDialect())),
+ DirectedReadOptionsUtil.toString(directedReadOptions),
+ SHOW_DIRECTED_READ);
+ }
+
@Override
public StatementResult statementSetOptimizerVersion(String optimizerVersion) {
getConnection().setOptimizerVersion(optimizerVersion);
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectedReadOptionsUtil.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectedReadOptionsUtil.java
new file mode 100644
index 00000000000..8b1f8a90199
--- /dev/null
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DirectedReadOptionsUtil.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner.connection;
+
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.common.base.Strings;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.util.JsonFormat;
+import com.google.spanner.v1.DirectedReadOptions;
+
+public class DirectedReadOptionsUtil {
+
+ /**
+ * Generates a valid JSON string for the given {@link DirectedReadOptions} that can be used with
+ * the JDBC driver.
+ */
+ public static String toString(DirectedReadOptions directedReadOptions) {
+ if (directedReadOptions == null
+ || DirectedReadOptions.getDefaultInstance().equals(directedReadOptions)) {
+ return "";
+ }
+ try {
+ return JsonFormat.printer().omittingInsignificantWhitespace().print(directedReadOptions);
+ } catch (InvalidProtocolBufferException invalidProtocolBufferException) {
+ throw SpannerExceptionFactory.asSpannerException(invalidProtocolBufferException);
+ }
+ }
+
+ static DirectedReadOptions parse(String json) {
+ if (Strings.isNullOrEmpty(json)) {
+ return DirectedReadOptions.getDefaultInstance();
+ }
+ DirectedReadOptions.Builder builder = DirectedReadOptions.newBuilder();
+ try {
+ JsonFormat.parser().merge(json, builder);
+ return builder.build();
+ } catch (InvalidProtocolBufferException invalidProtocolBufferException) {
+ throw SpannerExceptionFactory.asSpannerException(invalidProtocolBufferException);
+ }
+ }
+}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadOnlyTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadOnlyTransaction.java
index 5a005ff8052..63e5221362e 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadOnlyTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadOnlyTransaction.java
@@ -107,6 +107,11 @@ public boolean isReadOnly() {
return true;
}
+ @Override
+ public boolean supportsDirectedReads(ParsedStatement ignore) {
+ return true;
+ }
+
@Override
void checkAborted() {
// No-op for read-only transactions as they cannot abort.
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
index 52486ed43e1..56ddc16322a 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
@@ -188,6 +188,11 @@ public boolean isReadOnly() {
return readOnly;
}
+ @Override
+ public boolean supportsDirectedReads(ParsedStatement parsedStatement) {
+ return parsedStatement.isQuery();
+ }
+
private void checkAndMarkUsed() {
Preconditions.checkState(!used, "This single-use transaction has already been used");
used = true;
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java
index 1452d906a96..d180423266e 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java
@@ -63,6 +63,8 @@ enum ClientSideStatementType {
SHOW_COMMIT_RESPONSE,
SHOW_READ_ONLY_STALENESS,
SET_READ_ONLY_STALENESS,
+ SHOW_DIRECTED_READ,
+ SET_DIRECTED_READ,
SHOW_OPTIMIZER_VERSION,
SET_OPTIMIZER_VERSION,
SHOW_OPTIMIZER_STATISTICS_PACKAGE,
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/UnitOfWork.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/UnitOfWork.java
index ea315e94c0e..1c8bc6a29c0 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/UnitOfWork.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/UnitOfWork.java
@@ -128,6 +128,14 @@ public boolean isActive() {
/** @return true if this unit of work is read-only. */
boolean isReadOnly();
+ /**
+ * @return true if this unit of work supports {@link
+ * com.google.spanner.v1.DirectedReadOptions}
+ */
+ default boolean supportsDirectedReads(ParsedStatement parsedStatement) {
+ return false;
+ }
+
/**
* Executes a query with the given options. If {@link AnalyzeMode} is set to {@link
* AnalyzeMode#PLAN} or {@link AnalyzeMode#PROFILE}, the returned {@link ResultSet} will include
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
index a8618af5c79..c9aa5987663 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
@@ -339,7 +339,7 @@ public GapicSpannerRpc(final SpannerOptions options) {
SpannerInterceptorProvider.create(
MoreObjects.firstNonNull(
options.getInterceptorProvider(),
- SpannerInterceptorProvider.createDefault()))
+ SpannerInterceptorProvider.createDefault(options.getOpenTelemetry())))
// This sets the response compressor (Server -> Client).
.withEncoding(compressorName))
.setHeaderProvider(headerProviderWithUserAgent)
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java
index d4725b25427..7de63dc33ba 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/HeaderInterceptor.java
@@ -22,6 +22,7 @@
import static com.google.cloud.spanner.spi.v1.SpannerRpcViews.SPANNER_GFE_HEADER_MISSING_COUNT;
import static com.google.cloud.spanner.spi.v1.SpannerRpcViews.SPANNER_GFE_LATENCY;
+import com.google.cloud.spanner.SpannerRpcMetrics;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
@@ -37,6 +38,8 @@
import io.opencensus.tags.TagValue;
import io.opencensus.tags.Tagger;
import io.opencensus.tags.Tags;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
@@ -63,8 +66,23 @@ class HeaderInterceptor implements ClientInterceptor {
private static final Logger LOGGER = Logger.getLogger(HeaderInterceptor.class.getName());
private static final Level LEVEL = Level.INFO;
+ private final SpannerRpcMetrics spannerRpcMetrics;
- HeaderInterceptor() {}
+ HeaderInterceptor(SpannerRpcMetrics spannerRpcMetrics) {
+ this.spannerRpcMetrics = spannerRpcMetrics;
+ }
+
+ private class SpannerProperties {
+ String projectId;
+ String instanceId;
+ String databaseId;
+
+ SpannerProperties(String projectId, String instanceId, String databaseId) {
+ this.databaseId = databaseId;
+ this.instanceId = instanceId;
+ this.projectId = projectId;
+ }
+ }
@Override
public ClientCall interceptCall(
@@ -72,13 +90,14 @@ public ClientCall interceptCall(
return new SimpleForwardingClientCall(next.newCall(method, callOptions)) {
@Override
public void start(Listener responseListener, Metadata headers) {
- TagContext tagContext = getTagContext(headers, method.getFullMethodName());
+ SpannerProperties spannerProperties = createProjectPropertes(headers);
+ TagContext tagContext = getTagContext(method.getFullMethodName(), spannerProperties);
+ Attributes attributes = getMetricAttributes(method.getFullMethodName(), spannerProperties);
super.start(
new SimpleForwardingClientCallListener(responseListener) {
@Override
public void onHeaders(Metadata metadata) {
-
- processHeader(metadata, tagContext);
+ processHeader(metadata, tagContext, attributes);
super.onHeaders(metadata);
}
},
@@ -87,7 +106,7 @@ public void onHeaders(Metadata metadata) {
};
}
- private void processHeader(Metadata metadata, TagContext tagContext) {
+ private void processHeader(Metadata metadata, TagContext tagContext, Attributes attributes) {
MeasureMap measureMap = STATS_RECORDER.newMeasureMap();
if (metadata.get(SERVER_TIMING_HEADER_KEY) != null) {
String serverTiming = metadata.get(SERVER_TIMING_HEADER_KEY);
@@ -98,27 +117,20 @@ private void processHeader(Metadata metadata, TagContext tagContext) {
measureMap.put(SPANNER_GFE_LATENCY, latency);
measureMap.put(SPANNER_GFE_HEADER_MISSING_COUNT, 0L);
measureMap.record(tagContext);
+
+ spannerRpcMetrics.recordGfeLatency(latency, attributes);
+ spannerRpcMetrics.recordGfeHeaderMissingCount(0L, attributes);
} catch (NumberFormatException e) {
LOGGER.log(LEVEL, "Invalid server-timing object in header", matcher.group("dur"));
}
}
} else {
+ spannerRpcMetrics.recordGfeHeaderMissingCount(1L, attributes);
measureMap.put(SPANNER_GFE_HEADER_MISSING_COUNT, 1L).record(tagContext);
}
}
- private TagContext getTagContext(
- String method, String projectId, String instanceId, String databaseId) {
- return TAGGER
- .currentBuilder()
- .putLocal(PROJECT_ID, TagValue.create(projectId))
- .putLocal(INSTANCE_ID, TagValue.create(instanceId))
- .putLocal(DATABASE_ID, TagValue.create(databaseId))
- .putLocal(METHOD, TagValue.create(method))
- .build();
- }
-
- private TagContext getTagContext(Metadata headers, String method) {
+ private SpannerProperties createProjectPropertes(Metadata headers) {
String projectId = "undefined-project";
String instanceId = "undefined-database";
String databaseId = "undefined-database";
@@ -137,6 +149,26 @@ private TagContext getTagContext(Metadata headers, String method) {
LOGGER.log(LEVEL, "Error parsing google cloud resource header: " + googleResourcePrefix);
}
}
- return getTagContext(method, projectId, instanceId, databaseId);
+ return new SpannerProperties(projectId, instanceId, databaseId);
+ }
+
+ private TagContext getTagContext(String method, SpannerProperties spannerProperties) {
+ return TAGGER
+ .currentBuilder()
+ .putLocal(PROJECT_ID, TagValue.create(spannerProperties.projectId))
+ .putLocal(INSTANCE_ID, TagValue.create(spannerProperties.instanceId))
+ .putLocal(DATABASE_ID, TagValue.create(spannerProperties.databaseId))
+ .putLocal(METHOD, TagValue.create(method))
+ .build();
+ }
+
+ private Attributes getMetricAttributes(String method, SpannerProperties spannerProperties) {
+ AttributesBuilder attributesBuilder = Attributes.builder();
+ attributesBuilder.put("database", spannerProperties.databaseId);
+ attributesBuilder.put("instance_id", spannerProperties.instanceId);
+ attributesBuilder.put("project_id", spannerProperties.projectId);
+ attributesBuilder.put("method", method);
+
+ return attributesBuilder.build();
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java
index e45702de8df..9b1a2fd3c1f 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerInterceptorProvider.java
@@ -16,9 +16,14 @@
package com.google.cloud.spanner.spi.v1;
import com.google.api.core.InternalApi;
+import com.google.api.core.ObsoleteApi;
import com.google.api.gax.grpc.GrpcInterceptorProvider;
+import com.google.cloud.spanner.SpannerRpcMetrics;
import com.google.common.collect.ImmutableList;
import io.grpc.ClientInterceptor;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -29,21 +34,24 @@
*/
@InternalApi("Exposed for testing")
public class SpannerInterceptorProvider implements GrpcInterceptorProvider {
-
- private static final List defaultInterceptors =
- ImmutableList.of(
- new SpannerErrorInterceptor(),
- new LoggingInterceptor(Logger.getLogger(GapicSpannerRpc.class.getName()), Level.FINER),
- new HeaderInterceptor());
-
private final List clientInterceptors;
private SpannerInterceptorProvider(List clientInterceptors) {
this.clientInterceptors = clientInterceptors;
}
+ @ObsoleteApi("This method always uses Global OpenTelemetry")
public static SpannerInterceptorProvider createDefault() {
- return new SpannerInterceptorProvider(defaultInterceptors);
+ return createDefault(GlobalOpenTelemetry.get());
+ }
+
+ public static SpannerInterceptorProvider createDefault(OpenTelemetry openTelemetry) {
+ List defaultInterceptorList = new ArrayList<>();
+ defaultInterceptorList.add(new SpannerErrorInterceptor());
+ defaultInterceptorList.add(
+ new LoggingInterceptor(Logger.getLogger(GapicSpannerRpc.class.getName()), Level.FINER));
+ defaultInterceptorList.add(new HeaderInterceptor(new SpannerRpcMetrics(openTelemetry)));
+ return new SpannerInterceptorProvider(ImmutableList.copyOf(defaultInterceptorList));
}
static SpannerInterceptorProvider create(GrpcInterceptorProvider provider) {
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpcViews.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpcViews.java
index b1b68e95d1a..7d6cc163b46 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpcViews.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerRpcViews.java
@@ -15,6 +15,8 @@
*/
package com.google.cloud.spanner.spi.v1;
+import com.google.api.core.ObsoleteApi;
+import com.google.cloud.spanner.SpannerOptions;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import io.opencensus.stats.Aggregation;
@@ -91,28 +93,49 @@ public class SpannerRpcViews {
* measures the latency between Google's network receives an RPC and reads back the first byte of
* the response. gfe_header_missing_count is a counter of the number of RPC responses without a
* server-timing header.
+ *
+ * @deprecated The OpenCensus project is deprecated. Use OpenTelemetry to get gfe_latency and
+ * gfe_header_missing_count metrics.
*/
@VisibleForTesting
+ @ObsoleteApi(
+ "The OpenCensus project is deprecated. Use OpenTelemetry to get gfe_latency and gfe_header_missing_count metrics.")
public static void registerGfeLatencyAndHeaderMissingCountViews() {
- viewManager.registerView(SPANNER_GFE_LATENCY_VIEW);
- viewManager.registerView(SPANNER_GFE_HEADER_MISSING_COUNT_VIEW);
+ if (SpannerOptions.isEnabledOpenCensusMetrics()) {
+ viewManager.registerView(SPANNER_GFE_LATENCY_VIEW);
+ viewManager.registerView(SPANNER_GFE_HEADER_MISSING_COUNT_VIEW);
+ }
}
/**
* Register GFE Latency view. gfe_latency measures the latency between Google's network receives
* an RPC and reads back the first byte of the response.
+ *
+ * @deprecated The OpenCensus project is deprecated. Use OpenTelemetry to get gfe_latency and
+ * gfe_header_missing_count metrics.
*/
@VisibleForTesting
+ @ObsoleteApi(
+ "The OpenCensus project is deprecated. Use OpenTelemetry to get gfe_latency and gfe_header_missing_count metrics.")
public static void registerGfeLatencyView() {
- viewManager.registerView(SPANNER_GFE_LATENCY_VIEW);
+ if (SpannerOptions.isEnabledOpenCensusMetrics()) {
+ viewManager.registerView(SPANNER_GFE_LATENCY_VIEW);
+ }
}
/**
* Register GFE Header Missing Count view. gfe_header_missing_count is a counter of the number of
* RPC responses without a server-timing header.
+ *
+ * @deprecated The OpenCensus project is deprecated. Use OpenTelemetry to get gfe_latency and
+ * gfe_header_missing_count metrics.
*/
@VisibleForTesting
+ @ObsoleteApi(
+ "The OpenCensus project is deprecated. Use OpenTelemetry to get gfe_latency and gfe_header_missing_count metrics.")
public static void registerGfeHeaderMissingCountView() {
- viewManager.registerView(SPANNER_GFE_HEADER_MISSING_COUNT_VIEW);
+ if (SpannerOptions.isEnabledOpenCensusMetrics()) {
+ viewManager.registerView(SPANNER_GFE_HEADER_MISSING_COUNT_VIEW);
+ }
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java
index 4b9b82240c4..e2001364abb 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java
@@ -16,12 +16,15 @@
package com.google.cloud.spanner.testing;
+import com.google.api.client.util.BackOff;
+import com.google.api.client.util.ExponentialBackOff;
import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.BatchClient;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.InstanceId;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerException;
@@ -34,6 +37,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Random;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -54,7 +58,7 @@ public class RemoteSpannerHelper {
private static int dbRolePrefix = new Random().nextInt(Integer.MAX_VALUE);
private static final AtomicInteger backupSeq = new AtomicInteger();
private static final int backupPrefix = new Random().nextInt(Integer.MAX_VALUE);
- private final List dbs = new ArrayList<>();
+ private final List databaseIds = new ArrayList<>();
protected RemoteSpannerHelper(SpannerOptions options, InstanceId instanceId, Spanner client) {
this.options = options;
@@ -132,19 +136,47 @@ public String getUniqueBackupId() {
public Database createTestDatabase(Dialect dialect, Iterable statements)
throws SpannerException {
String dbId = getUniqueDatabaseId();
+ DatabaseId databaseId = DatabaseId.of(instanceId.getProject(), instanceId.getInstance(), dbId);
Database databaseToCreate =
- client
- .getDatabaseAdminClient()
- .newDatabaseBuilder(
- DatabaseId.of(instanceId.getProject(), instanceId.getInstance(), dbId))
- .setDialect(dialect)
- .build();
+ client.getDatabaseAdminClient().newDatabaseBuilder(databaseId).setDialect(dialect).build();
try {
Iterable ddlStatements =
dialect == Dialect.POSTGRESQL ? Collections.emptyList() : statements;
- OperationFuture op =
- client.getDatabaseAdminClient().createDatabase(databaseToCreate, ddlStatements);
- Database db = op.get();
+ Database db = null;
+ final int maxAttempts = 20;
+ BackOff backOff =
+ new ExponentialBackOff.Builder()
+ .setInitialIntervalMillis(10_000)
+ .setMaxIntervalMillis(60_000)
+ .setMaxElapsedTimeMillis(120_000)
+ .build();
+ for (int attempts = 0; attempts < maxAttempts; attempts++) {
+ try {
+ OperationFuture op =
+ client.getDatabaseAdminClient().createDatabase(databaseToCreate, ddlStatements);
+ db = op.get();
+ break;
+ } catch (ExecutionException executionException) {
+ SpannerException spannerException =
+ SpannerExceptionFactory.asSpannerException(executionException.getCause());
+ if (spannerException.getErrorCode() != ErrorCode.RESOURCE_EXHAUSTED) {
+ throw executionException;
+ }
+ } catch (SpannerException spannerException) {
+ if (spannerException.getErrorCode() != ErrorCode.RESOURCE_EXHAUSTED) {
+ throw spannerException;
+ }
+ }
+ long sleep = backOff.nextBackOffMillis();
+ if (sleep > 0L) {
+ Thread.sleep(sleep);
+ }
+ }
+ if (db == null) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.RESOURCE_EXHAUSTED,
+ String.format("Failed to create test database after %d attempts", maxAttempts));
+ }
if (dialect == Dialect.POSTGRESQL && Iterables.size(statements) > 0) {
client
.getDatabaseAdminClient()
@@ -152,10 +184,11 @@ public Database createTestDatabase(Dialect dialect, Iterable statements)
.get();
}
logger.log(Level.FINE, "Created test database {0}", db.getId());
- dbs.add(db);
return db;
} catch (Exception e) {
throw SpannerExceptionFactory.newSpannerException(e);
+ } finally {
+ databaseIds.add(databaseId);
}
}
@@ -167,13 +200,15 @@ public Database createTestDatabase(Iterable statements) throws SpannerEx
public void cleanUp() {
// Drop all the databases we created explicitly.
int numDropped = 0;
- for (Database db : dbs) {
+ for (DatabaseId databaseId : databaseIds) {
try {
- logger.log(Level.INFO, "Dropping test database {0}", db.getId());
- db.drop();
+ logger.log(Level.INFO, "Dropping test database {0}", databaseId);
+ client
+ .getDatabaseAdminClient()
+ .dropDatabase(databaseId.getInstanceId().getInstance(), databaseId.getDatabase());
++numDropped;
- } catch (SpannerException e) {
- logger.log(Level.SEVERE, "Failed to drop test database " + db.getId(), e);
+ } catch (Throwable e) {
+ logger.log(Level.SEVERE, "Failed to drop test database " + databaseId, e);
}
}
logger.log(Level.INFO, "Dropped {0} test database(s)", numDropped);
diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
index 576991b8e02..20d7522961d 100644
--- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
+++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
@@ -76,6 +76,15 @@
"method": "statementShowReadOnlyStaleness",
"exampleStatements": ["show variable read_only_staleness"]
},
+ {
+ "name": "SHOW VARIABLE DIRECTED_READ",
+ "executorName": "ClientSideStatementNoParamExecutor",
+ "resultType": "RESULT_SET",
+ "statementType": "SHOW_DIRECTED_READ",
+ "regex": "(?is)\\A\\s*show\\s+variable\\s+directed_read\\s*\\z",
+ "method": "statementShowDirectedRead",
+ "exampleStatements": ["show variable directed_read"]
+ },
{
"name": "SHOW VARIABLE OPTIMIZER_VERSION",
"executorName": "ClientSideStatementNoParamExecutor",
@@ -373,6 +382,21 @@
"converterName": "ClientSideStatementValueConverters$ReadOnlyStalenessConverter"
}
},
+ {
+ "name": "SET DIRECTED_READ = ''|''",
+ "executorName": "ClientSideStatementSetExecutor",
+ "resultType": "NO_RESULT",
+ "statementType": "SET_DIRECTED_READ",
+ "regex": "(?is)\\A\\s*set\\s+directed_read\\s*(?:=)\\s*(.*)\\z",
+ "method": "statementSetDirectedRead",
+ "exampleStatements": ["set directed_read='{\"includeReplicas\":{\"replicaSelections\":[{\"location\":\"eu-west1\",\"type\":\"READ_ONLY\"}]}}'", "set directed_read=''"],
+ "setStatement": {
+ "propertyName": "DIRECTED_READ",
+ "separator": "=",
+ "allowedValues": "'((\\S+)|())'",
+ "converterName": "ClientSideStatementValueConverters$DirectedReadOptionsConverter"
+ }
+ },
{
"name": "SET OPTIMIZER_VERSION = ''|'LATEST'|''",
"executorName": "ClientSideStatementSetExecutor",
diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json
index cb601d7277c..bd04b725c29 100644
--- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json
+++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json
@@ -76,6 +76,15 @@
"method": "statementShowReadOnlyStaleness",
"exampleStatements": ["show spanner.read_only_staleness","show variable spanner.read_only_staleness"]
},
+ {
+ "name": "SHOW [VARIABLE] SPANNER.DIRECTED_READ",
+ "executorName": "ClientSideStatementNoParamExecutor",
+ "resultType": "RESULT_SET",
+ "statementType": "SHOW_DIRECTED_READ",
+ "regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.directed_read\\s*\\z",
+ "method": "statementShowDirectedRead",
+ "exampleStatements": ["show spanner.directed_read", "show variable spanner.directed_read"]
+ },
{
"name": "SHOW [VARIABLE] SPANNER.OPTIMIZER_VERSION",
"executorName": "ClientSideStatementNoParamExecutor",
@@ -525,6 +534,21 @@
"converterName": "ClientSideStatementValueConverters$ReadOnlyStalenessConverter"
}
},
+ {
+ "name": "SET SPANNER.DIRECTED_READ =|TO ''|''",
+ "executorName": "ClientSideStatementSetExecutor",
+ "resultType": "NO_RESULT",
+ "statementType": "SET_DIRECTED_READ",
+ "regex": "(?is)\\A\\s*set\\s+spanner\\.directed_read(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
+ "method": "statementSetDirectedRead",
+ "exampleStatements": ["set spanner.directed_read='{\"includeReplicas\":{\"replicaSelections\":[{\"location\":\"eu-west1\",\"type\":\"READ_ONLY\"}]}}'", "set spanner.directed_read=''"],
+ "setStatement": {
+ "propertyName": "SPANNER.DIRECTED_READ",
+ "separator": "(?:=|\\s+TO\\s+)",
+ "allowedValues": "'((\\S+)|())'",
+ "converterName": "ClientSideStatementValueConverters$DirectedReadOptionsConverter"
+ }
+ },
{
"name": "SET SPANNER.OPTIMIZER_VERSION =|TO ''|'LATEST'|''",
"executorName": "ClientSideStatementSetExecutor",
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerImplTest.java
index 84f7fb0db9b..08d22dd2d67 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerImplTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerImplTest.java
@@ -22,7 +22,8 @@
import com.google.api.core.ApiFutures;
import com.google.cloud.Timestamp;
-import io.opencensus.trace.Span;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Scope;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -36,8 +37,11 @@ public class AsyncTransactionManagerImplTest {
@Test
public void testCommitReturnsCommitStats() {
+ Span oTspan = mock(Span.class);
+ ISpan span = new OpenTelemetrySpan(oTspan);
+ when(oTspan.makeCurrent()).thenReturn(mock(Scope.class));
try (AsyncTransactionManagerImpl manager =
- new AsyncTransactionManagerImpl(session, mock(Span.class), Options.commitStats())) {
+ new AsyncTransactionManagerImpl(session, span, Options.commitStats())) {
when(session.newTransaction(Options.fromTransactionOptions(Options.commitStats())))
.thenReturn(transaction);
when(transaction.ensureTxnAsync()).thenReturn(ApiFutures.immediateFuture(null));
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BatchClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BatchClientImplTest.java
index 18ae8a07b35..b7c4834044a 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BatchClientImplTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BatchClientImplTest.java
@@ -35,9 +35,11 @@
import com.google.protobuf.util.Timestamps;
import com.google.spanner.v1.Session;
import com.google.spanner.v1.Transaction;
+import io.opentelemetry.api.OpenTelemetry;
import java.util.Collections;
import java.util.Map;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -62,6 +64,12 @@ public final class BatchClientImplTest {
private BatchClient client;
+ @BeforeClass
+ public static void setupOpenTelemetry() {
+ SpannerOptions.resetActiveTracingFramework();
+ SpannerOptions.enableOpenTelemetryTraces();
+ }
+
@SuppressWarnings("unchecked")
@Before
public void setUp() {
@@ -74,6 +82,7 @@ public void setUp() {
when(spannerOptions.getClock()).thenReturn(NanoClock.getDefaultClock());
when(spannerOptions.getSpannerRpcV1()).thenReturn(gapicRpc);
when(spannerOptions.getSessionLabels()).thenReturn(Collections.emptyMap());
+ when(spannerOptions.getOpenTelemetry()).thenReturn(OpenTelemetry.noop());
GrpcTransportOptions transportOptions = mock(GrpcTransportOptions.class);
when(transportOptions.getExecutorFactory()).thenReturn(mock(ExecutorFactory.class));
when(spannerOptions.getTransportOptions()).thenReturn(transportOptions);
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
index 2c2d5a1a139..ccbf3c0b2b9 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
@@ -102,6 +102,8 @@
import io.grpc.StatusRuntimeException;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.protobuf.lite.ProtoLiteUtils;
+import io.opencensus.trace.Tracing;
+import io.opentelemetry.api.OpenTelemetry;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -133,6 +135,7 @@
@RunWith(JUnit4.class)
public class DatabaseClientImplTest {
+
private static final String TEST_PROJECT = "my-project";
private static final String TEST_INSTANCE = "my-instance";
private static final String TEST_DATABASE = "my-database";
@@ -3374,7 +3377,10 @@ public void testReadWriteTransaction_usesOptions() {
when(pool.getSession()).thenReturn(session);
TransactionOption option = mock(TransactionOption.class);
- DatabaseClientImpl client = new DatabaseClientImpl(pool);
+ TraceWrapper traceWrapper =
+ new TraceWrapper(Tracing.getTracer(), OpenTelemetry.noop().getTracer(""));
+
+ DatabaseClientImpl client = new DatabaseClientImpl(pool, traceWrapper);
client.readWriteTransaction(option);
verify(session).readWriteTransaction(option);
@@ -3387,7 +3393,7 @@ public void testTransactionManager_usesOptions() {
when(pool.getSession()).thenReturn(session);
TransactionOption option = mock(TransactionOption.class);
- DatabaseClientImpl client = new DatabaseClientImpl(pool);
+ DatabaseClientImpl client = new DatabaseClientImpl(pool, mock(TraceWrapper.class));
try (TransactionManager ignore = client.transactionManager(option)) {
verify(session).transactionManager(option);
}
@@ -3400,7 +3406,7 @@ public void testRunAsync_usesOptions() {
when(pool.getSession()).thenReturn(session);
TransactionOption option = mock(TransactionOption.class);
- DatabaseClientImpl client = new DatabaseClientImpl(pool);
+ DatabaseClientImpl client = new DatabaseClientImpl(pool, mock(TraceWrapper.class));
client.runAsync(option);
verify(session).runAsync(option);
@@ -3413,7 +3419,7 @@ public void testTransactionManagerAsync_usesOptions() {
when(pool.getSession()).thenReturn(session);
TransactionOption option = mock(TransactionOption.class);
- DatabaseClientImpl client = new DatabaseClientImpl(pool);
+ DatabaseClientImpl client = new DatabaseClientImpl(pool, mock(TraceWrapper.class));
try (AsyncTransactionManager ignore = client.transactionManagerAsync(option)) {
verify(session).transactionManagerAsync(option);
}
@@ -3635,6 +3641,112 @@ public void testAsyncTransactionManagerCommitWithPriority() {
assertEquals(Priority.PRIORITY_HIGH, request.getRequestOptions().getPriority());
}
+ @Test
+ public void testCommitWithoutMaxCommitDelay() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ TransactionRunner runner = client.readWriteTransaction();
+ runner.run(
+ transaction -> {
+ transaction.buffer(Mutation.delete("TEST", KeySet.all()));
+ return null;
+ });
+
+ List requests = mockSpanner.getRequestsOfType(CommitRequest.class);
+ assertThat(requests).hasSize(1);
+ CommitRequest request = requests.get(0);
+ assertFalse(request.hasMaxCommitDelay());
+ }
+
+ @Test
+ public void testCommitWithMaxCommitDelay() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ TransactionRunner runner =
+ client.readWriteTransaction(Options.maxCommitDelay(java.time.Duration.ofMillis(100)));
+ runner.run(
+ transaction -> {
+ transaction.buffer(Mutation.delete("TEST", KeySet.all()));
+ return null;
+ });
+
+ List requests = mockSpanner.getRequestsOfType(CommitRequest.class);
+ assertThat(requests).hasSize(1);
+ CommitRequest request = requests.get(0);
+ assertNotNull(request.getMaxCommitDelay());
+ assertEquals(
+ com.google.protobuf.Duration.newBuilder().setNanos(100000000).build(),
+ request.getMaxCommitDelay());
+ }
+
+ @Test
+ public void testTransactionManagerCommitWithMaxCommitDelay() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ TransactionManager manager =
+ client.transactionManager(Options.maxCommitDelay(java.time.Duration.ofMillis(100)));
+ TransactionContext transaction = manager.begin();
+ transaction.buffer(Mutation.delete("TEST", KeySet.all()));
+ manager.commit();
+
+ List requests = mockSpanner.getRequestsOfType(CommitRequest.class);
+ assertThat(requests).hasSize(1);
+ CommitRequest request = requests.get(0);
+ assertNotNull(request.getMaxCommitDelay());
+ assertEquals(
+ com.google.protobuf.Duration.newBuilder().setNanos(100000000).build(),
+ request.getMaxCommitDelay());
+ }
+
+ @Test
+ public void testAsyncRunnerCommitWithMaxCommitDelay() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ AsyncRunner runner = client.runAsync(Options.maxCommitDelay(java.time.Duration.ofMillis(100)));
+ get(
+ runner.runAsync(
+ txn -> {
+ txn.buffer(Mutation.delete("TEST", KeySet.all()));
+ return ApiFutures.immediateFuture(null);
+ },
+ executor));
+
+ List requests = mockSpanner.getRequestsOfType(CommitRequest.class);
+ assertThat(requests).hasSize(1);
+ CommitRequest request = requests.get(0);
+ assertNotNull(request.getMaxCommitDelay());
+ assertEquals(
+ com.google.protobuf.Duration.newBuilder().setNanos(100000000).build(),
+ request.getMaxCommitDelay());
+ }
+
+ @Test
+ public void testAsyncTransactionManagerCommitWithMaxCommitDelay() {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ try (AsyncTransactionManager manager =
+ client.transactionManagerAsync(Options.maxCommitDelay(java.time.Duration.ofMillis(100)))) {
+ TransactionContextFuture transaction = manager.beginAsync();
+ get(
+ transaction
+ .then(
+ (txn, input) -> {
+ txn.buffer(Mutation.delete("TEST", KeySet.all()));
+ return ApiFutures.immediateFuture(null);
+ },
+ executor)
+ .commitAsync());
+ }
+
+ List requests = mockSpanner.getRequestsOfType(CommitRequest.class);
+ assertThat(requests).hasSize(1);
+ CommitRequest request = requests.get(0);
+ assertNotNull(request.getMaxCommitDelay());
+ assertEquals(
+ com.google.protobuf.Duration.newBuilder().setNanos(100000000).build(),
+ request.getMaxCommitDelay());
+ }
+
@Test
public void singleUseNoAction_ClearsCheckedOutSession() {
DatabaseClientImpl client =
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/FailOnOverkillTraceComponentImpl.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/FailOnOverkillTraceComponentImpl.java
index 4877c60837f..4d9b4ddd805 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/FailOnOverkillTraceComponentImpl.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/FailOnOverkillTraceComponentImpl.java
@@ -29,6 +29,7 @@
import io.opencensus.trace.SpanBuilder;
import io.opencensus.trace.SpanContext;
import io.opencensus.trace.SpanId;
+import io.opencensus.trace.Status;
import io.opencensus.trace.TraceComponent;
import io.opencensus.trace.TraceId;
import io.opencensus.trace.TraceOptions;
@@ -43,6 +44,7 @@
import io.opencensus.trace.propagation.BinaryFormat;
import io.opencensus.trace.propagation.PropagationComponent;
import io.opencensus.trace.propagation.TextFormat;
+import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -62,6 +64,8 @@ public class FailOnOverkillTraceComponentImpl extends TraceComponent {
private final TraceConfig traceConfig = new TestTraceConfig();
private static final Map spans = new LinkedHashMap<>();
+ private static final List annotations = new ArrayList<>();
+
public static class TestSpan extends Span {
@GuardedBy("this")
private volatile boolean ended = false;
@@ -75,14 +79,27 @@ private TestSpan(String spanName, SpanContext context, EnumSet options)
}
@Override
- public void addAnnotation(String description, Map attributes) {}
+ public void addAnnotation(String description, Map attributes) {
+ annotations.add(description);
+ }
+
+ @Override
+ public void addAnnotation(Annotation annotation) {
+ annotations.add(annotation.getDescription());
+ }
+
+ @Override
+ public void putAttributes(Map attributes) {}
@Override
- public void addAnnotation(Annotation annotation) {}
+ public void addAttributes(Map attributes) {}
@Override
public void addLink(Link link) {}
+ @Override
+ public void setStatus(Status status) {}
+
@Override
public void end(EndSpanOptions options) {
synchronized (this) {
@@ -210,10 +227,18 @@ Map getSpans() {
return spans;
}
+ List getAnnotations() {
+ return annotations;
+ }
+
void clearSpans() {
spans.clear();
}
+ void clearAnnotations() {
+ annotations.clear();
+ }
+
@Override
public PropagationComponent getPropagationComponent() {
return propagationComponent;
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ITSessionPoolIntegrationTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ITSessionPoolIntegrationTest.java
index 36b9bee0af7..be9f6841f20 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ITSessionPoolIntegrationTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ITSessionPoolIntegrationTest.java
@@ -20,6 +20,8 @@
import com.google.cloud.grpc.GrpcTransportOptions.ExecutorFactory;
import com.google.cloud.spanner.SessionPool.PooledSessionFuture;
+import io.opencensus.trace.Tracing;
+import io.opentelemetry.api.OpenTelemetry;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -93,7 +95,9 @@ public ScheduledExecutorService get() {
return new ScheduledThreadPoolExecutor(2);
}
},
- ((SpannerImpl) env.getTestHelper().getClient()).getSessionClient(db.getId()));
+ ((SpannerImpl) env.getTestHelper().getClient()).getSessionClient(db.getId()),
+ new TraceWrapper(Tracing.getTracer(), OpenTelemetry.noop().getTracer("")),
+ OpenTelemetry.noop());
}
@Test
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java
index 2c59439ce7d..4d4f639d3d9 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java
@@ -20,8 +20,8 @@
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.gax.longrunning.OperationFuture;
-import com.google.api.gax.paging.Page;
import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.DatabaseInfo.DatabaseField;
import com.google.cloud.spanner.testing.EmulatorSpannerHelper;
import com.google.cloud.spanner.testing.RemoteSpannerHelper;
import com.google.common.collect.Iterators;
@@ -210,30 +210,32 @@ static boolean isRetryableResourceExhaustedException(SpannerException exception)
}
private void cleanUpOldDatabases(InstanceId instanceId) {
- long OLD_DB_THRESHOLD_SECS = TimeUnit.SECONDS.convert(24L, TimeUnit.HOURS);
+ long OLD_DB_THRESHOLD_SECS = TimeUnit.SECONDS.convert(6L, TimeUnit.HOURS);
Timestamp currentTimestamp = Timestamp.now();
int numDropped = 0;
- Page page = databaseAdminClient.listDatabases(instanceId.getInstance());
String TEST_DB_REGEX = "(testdb_(.*)_(.*))|(mysample-(.*))";
logger.log(Level.INFO, "Dropping old test databases from {0}", instanceId.getName());
- while (page != null) {
- for (Database db : page.iterateAll()) {
- try {
- long timeDiff = currentTimestamp.getSeconds() - db.getCreateTime().getSeconds();
- // Delete all databases which are more than OLD_DB_THRESHOLD_SECS seconds
- // old.
- if ((db.getId().getDatabase().matches(TEST_DB_REGEX))
- && (timeDiff > OLD_DB_THRESHOLD_SECS)) {
- logger.log(Level.INFO, "Dropping test database {0}", db.getId());
- db.drop();
- ++numDropped;
+ for (Database db : databaseAdminClient.listDatabases(instanceId.getInstance()).iterateAll()) {
+ try {
+ long timeDiff = currentTimestamp.getSeconds() - db.getCreateTime().getSeconds();
+ // Delete all databases which are more than OLD_DB_THRESHOLD_SECS seconds old.
+ if ((db.getId().getDatabase().matches(TEST_DB_REGEX))
+ && (timeDiff > OLD_DB_THRESHOLD_SECS)) {
+ logger.log(Level.INFO, "Dropping test database {0}", db.getId());
+ if (db.isDropProtectionEnabled()) {
+ Database updatedDatabase =
+ databaseAdminClient.newDatabaseBuilder(db.getId()).disableDropProtection().build();
+ databaseAdminClient
+ .updateDatabase(updatedDatabase, DatabaseField.DROP_PROTECTION)
+ .get();
}
- } catch (SpannerException e) {
- logger.log(Level.SEVERE, "Failed to drop test database " + db.getId(), e);
+ db.drop();
+ ++numDropped;
}
+ } catch (SpannerException | ExecutionException | InterruptedException e) {
+ logger.log(Level.SEVERE, "Failed to drop test database " + db.getId(), e);
}
- page = page.getNextPage();
}
logger.log(Level.INFO, "Dropped {0} test database(s)", numDropped);
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java
index 0fa5ff27d82..62a25f9dc4d 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestWithClosedSessionsEnv.java
@@ -46,7 +46,7 @@ private static class SpannerWithClosedSessionsImpl extends SpannerImpl {
@Override
DatabaseClientImpl createDatabaseClient(String clientId, SessionPool pool) {
- return new DatabaseClientWithClosedSessionImpl(clientId, pool);
+ return new DatabaseClientWithClosedSessionImpl(clientId, pool, tracer);
}
}
@@ -58,8 +58,8 @@ public static class DatabaseClientWithClosedSessionImpl extends DatabaseClientIm
private boolean invalidateNextSession = false;
private boolean allowReplacing = true;
- DatabaseClientWithClosedSessionImpl(String clientId, SessionPool pool) {
- super(clientId, pool);
+ DatabaseClientWithClosedSessionImpl(String clientId, SessionPool pool, TraceWrapper tracer) {
+ super(clientId, pool, tracer);
}
/** Invalidate the next session that is checked out from the pool. */
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java
new file mode 100644
index 00000000000..956607e0231
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/OpenTelemetrySpanTest.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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.
+ */
+
+package com.google.cloud.spanner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.google.api.gax.grpc.testing.LocalChannelProvider;
+import com.google.cloud.NoCredentials;
+import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ListValue;
+import com.google.spanner.v1.ResultSetMetadata;
+import com.google.spanner.v1.StructType;
+import com.google.spanner.v1.StructType.Field;
+import com.google.spanner.v1.TypeCode;
+import io.grpc.Server;
+import io.grpc.Status;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.opencensus.trace.Tracing;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.data.EventData;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@Category(TracerTest.class)
+@RunWith(JUnit4.class)
+public class OpenTelemetrySpanTest {
+
+ private static final String TEST_PROJECT = "my-project";
+ private static final String TEST_INSTANCE = "my-instance";
+ private static final String TEST_DATABASE = "my-database";
+ private static LocalChannelProvider channelProvider;
+ private static MockSpannerServiceImpl mockSpanner;
+ private Spanner spanner;
+ private DatabaseClient client;
+ private static Server server;
+ private static InMemorySpanExporter spanExporter;
+
+ private static FailOnOverkillTraceComponentImpl failOnOverkillTraceComponent =
+ new FailOnOverkillTraceComponentImpl();
+
+ private static final Statement SELECT1 = Statement.of("SELECT 1 AS COL1");
+
+ private static final ResultSetMetadata SELECT1_METADATA =
+ ResultSetMetadata.newBuilder()
+ .setRowType(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("COL1")
+ .setType(
+ com.google.spanner.v1.Type.newBuilder()
+ .setCode(TypeCode.INT64)
+ .build())
+ .build())
+ .build())
+ .build();
+ private static final com.google.spanner.v1.ResultSet SELECT1_RESULTSET =
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(com.google.protobuf.Value.newBuilder().setStringValue("1").build())
+ .build())
+ .setMetadata(SELECT1_METADATA)
+ .build();
+ private static final Statement UPDATE_STATEMENT =
+ Statement.of("UPDATE FOO SET BAR=1 WHERE BAZ=2");
+ private static final long UPDATE_COUNT = 1L;
+ private static final Statement INVALID_UPDATE_STATEMENT =
+ Statement.of("UPDATE NON_EXISTENT_TABLE SET BAR=1 WHERE BAZ=2");
+
+ private List expectedBatchCreateSessionsRequestEvents =
+ ImmutableList.of("Requesting 25 sessions", "Request for 25 sessions returned 25 sessions");
+
+ private int expectedBatchCreateSessionsRequestEventsCount = 2;
+
+ private List expectedBatchCreateSessionsEvents = ImmutableList.of("Creating 25 sessions");
+
+ private int expectedBatchCreateSessionsEventsCount = 1;
+
+ private List expectedExecuteStreamingQueryEvents =
+ ImmutableList.of("Starting/Resuming stream");
+
+ private int expectedExecuteStreamingQueryEventsCount = 1;
+
+ private List expectedReadOnlyTransactionSingleUseEvents =
+ ImmutableList.of(
+ "Acquiring session",
+ "No session available",
+ "Creating sessions",
+ "Waiting for a session to come available",
+ "Using Session");
+
+ private int expectedReadOnlyTransactionSingleUseEventsCount = 5;
+
+ private List expectedReadOnlyTransactionMultiUseEvents =
+ ImmutableList.of(
+ "Acquiring session",
+ "No session available",
+ "Creating sessions",
+ "Waiting for a session to come available",
+ "Using Session",
+ "Creating Transaction",
+ "Transaction Creation Done");
+
+ private int expectedReadOnlyTransactionMultiUseEventsCount = 7;
+
+ private List expectedReadWriteTransactionErrorEvents =
+ ImmutableList.of(
+ "Acquiring session",
+ "No session available",
+ "Creating sessions",
+ "Waiting for a session to come available",
+ "Using Session",
+ "Starting Transaction Attempt",
+ "Transaction Attempt Failed in user operation",
+ "exception");
+
+ private int expectedReadWriteTransactionErrorEventsCount = 8;
+ private List expectedReadWriteTransactionEvents =
+ ImmutableList.of(
+ "Acquiring session",
+ "No session available",
+ "Creating sessions",
+ "Waiting for a session to come available",
+ "Using Session",
+ "Starting Transaction Attempt",
+ "Starting Commit",
+ "Commit Done",
+ "Transaction Attempt Succeeded");
+
+ private int expectedReadWriteTransactionCount = 9;
+ private List expectedReadWriteTransactionErrorWithBeginTransactionEvents =
+ ImmutableList.of(
+ "Acquiring session",
+ "No session available",
+ "Creating sessions",
+ "Waiting for a session to come available",
+ "Using Session",
+ "Starting Transaction Attempt",
+ "Transaction Attempt Aborted in user operation. Retrying",
+ "Creating Transaction",
+ "Transaction Creation Done",
+ "Starting Commit",
+ "Commit Done",
+ "Transaction Attempt Succeeded");
+
+ private int expectedReadWriteTransactionErrorWithBeginTransactionEventsCount = 13;
+ private List expectedReadOnlyTransactionSpans =
+ ImmutableList.of(
+ "CloudSpannerOperation.BatchCreateSessionsRequest",
+ "CloudSpannerOperation.ExecuteStreamingQuery",
+ "CloudSpannerOperation.BatchCreateSessions",
+ "CloudSpanner.ReadOnlyTransaction",
+ "SessionPool.WaitForSession");
+
+ private List expectedReadWriteTransactionWithCommitSpans =
+ ImmutableList.of(
+ "CloudSpannerOperation.BatchCreateSessionsRequest",
+ "CloudSpannerOperation.Commit",
+ "CloudSpannerOperation.BatchCreateSessions",
+ "CloudSpanner.ReadWriteTransaction",
+ "SessionPool.WaitForSession");
+
+ private List expectedReadWriteTransactionSpans =
+ ImmutableList.of(
+ "CloudSpannerOperation.BatchCreateSessionsRequest",
+ "CloudSpannerOperation.BatchCreateSessions",
+ "CloudSpanner.ReadWriteTransaction",
+ "SessionPool.WaitForSession");
+
+ private List expectedReadWriteTransactionWithCommitAndBeginTransactionSpans =
+ ImmutableList.of(
+ "CloudSpannerOperation.BeginTransaction",
+ "CloudSpannerOperation.BatchCreateSessionsRequest",
+ "CloudSpannerOperation.Commit",
+ "CloudSpannerOperation.BatchCreateSessions",
+ "CloudSpanner.ReadWriteTransaction",
+ "SessionPool.WaitForSession");
+
+ @BeforeClass
+ public static void setupOpenTelemetry() {
+ SpannerOptions.resetActiveTracingFramework();
+ SpannerOptions.enableOpenTelemetryTraces();
+ }
+
+ @BeforeClass
+ public static void startStaticServer() throws Exception {
+ // Incorporating OpenCensus tracer to ensure that OpenTraces traces are utilized if enabled,
+ // regardless of the presence of OpenCensus tracer.
+ java.lang.reflect.Field field = Tracing.class.getDeclaredField("traceComponent");
+ field.setAccessible(true);
+ java.lang.reflect.Field modifiersField = null;
+ try {
+ modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers");
+ } catch (NoSuchFieldException e) {
+ // Halt the test and ignore it.
+ Assume.assumeTrue(
+ "Skipping test as reflection is not allowed on reflection class in this JDK build",
+ false);
+ }
+ modifiersField.setAccessible(true);
+ // Remove the final modifier from the 'traceComponent' field.
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+ field.set(null, failOnOverkillTraceComponent);
+
+ mockSpanner = new MockSpannerServiceImpl();
+ mockSpanner.setAbortProbability(0.0D); // We don't want any unpredictable aborted transactions.
+ mockSpanner.putStatementResult(StatementResult.query(SELECT1, SELECT1_RESULTSET));
+ mockSpanner.putStatementResult(StatementResult.update(UPDATE_STATEMENT, UPDATE_COUNT));
+ mockSpanner.putStatementResult(
+ StatementResult.exception(
+ INVALID_UPDATE_STATEMENT,
+ Status.INVALID_ARGUMENT.withDescription("invalid statement").asRuntimeException()));
+ String uniqueName = InProcessServerBuilder.generateName();
+ server = InProcessServerBuilder.forName(uniqueName).addService(mockSpanner).build().start();
+
+ channelProvider = LocalChannelProvider.create(uniqueName);
+ failOnOverkillTraceComponent.clearSpans();
+ failOnOverkillTraceComponent.clearAnnotations();
+ }
+
+ @AfterClass
+ public static void stopServer() throws InterruptedException {
+ if (server != null) {
+ server.shutdown();
+ server.awaitTermination();
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ spanExporter = InMemorySpanExporter.create();
+
+ SdkTracerProvider tracerProvider =
+ SdkTracerProvider.builder()
+ .addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
+ .build();
+
+ OpenTelemetry openTelemetry =
+ OpenTelemetrySdk.builder()
+ .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
+ .setTracerProvider(tracerProvider)
+ .build();
+
+ SpannerOptions.Builder builder =
+ SpannerOptions.newBuilder()
+ .setProjectId(TEST_PROJECT)
+ .setChannelProvider(channelProvider)
+ .setOpenTelemetry(openTelemetry)
+ .setCredentials(NoCredentials.getInstance())
+ .setSessionPoolOption(SessionPoolOptions.newBuilder().setMinSessions(0).build());
+
+ spanner = builder.build().getService();
+
+ client = spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
+ }
+
+ @After
+ public void tearDown() {
+ spanner.close();
+ mockSpanner.reset();
+ mockSpanner.removeAllExecutionTimes();
+ spanExporter.reset();
+ }
+
+ @Test
+ public void singleUse() {
+ try (ResultSet rs = client.singleUse().executeQuery(SELECT1)) {
+ while (rs.next()) {
+ // Just consume the result set.
+ }
+ }
+
+ // OpenCensus spans should be 0 as OpenTelemetry is enabled.
+ assertEquals(failOnOverkillTraceComponent.getSpans().size(), 0);
+
+ List actualSpanItems = new ArrayList<>();
+ spanExporter
+ .getFinishedSpanItems()
+ .forEach(
+ spanItem -> {
+ actualSpanItems.add(spanItem.getName());
+ switch (spanItem.getName()) {
+ case "CloudSpannerOperation.BatchCreateSessionsRequest":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsRequestEvents,
+ expectedBatchCreateSessionsRequestEventsCount);
+ break;
+ case "CloudSpannerOperation.BatchCreateSessions":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsEvents,
+ expectedBatchCreateSessionsEventsCount);
+ break;
+ case "CloudSpannerOperation.ExecuteStreamingQuery":
+ verifyRequestEvents(
+ spanItem,
+ expectedExecuteStreamingQueryEvents,
+ expectedExecuteStreamingQueryEventsCount);
+ break;
+ case "CloudSpanner.ReadOnlyTransaction":
+ verifyRequestEvents(
+ spanItem,
+ expectedReadOnlyTransactionSingleUseEvents,
+ expectedReadOnlyTransactionSingleUseEventsCount);
+ break;
+ case "SessionPool.WaitForSession":
+ assertEquals(0, spanItem.getEvents().size());
+ break;
+ default:
+ assert false;
+ }
+ });
+
+ verifySpans(actualSpanItems, expectedReadOnlyTransactionSpans);
+ }
+
+ @Test
+ public void multiUse() {
+ try (ReadOnlyTransaction tx = client.readOnlyTransaction()) {
+ try (ResultSet rs = tx.executeQuery(SELECT1)) {
+ while (rs.next()) {
+ // Just consume the result set.
+ }
+ }
+ }
+
+ List actualSpanItems = new ArrayList<>();
+ spanExporter
+ .getFinishedSpanItems()
+ .forEach(
+ spanItem -> {
+ actualSpanItems.add(spanItem.getName());
+ switch (spanItem.getName()) {
+ case "CloudSpannerOperation.BatchCreateSessionsRequest":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsRequestEvents,
+ expectedBatchCreateSessionsRequestEventsCount);
+ break;
+ case "CloudSpannerOperation.BatchCreateSessions":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsEvents,
+ expectedBatchCreateSessionsEventsCount);
+ break;
+ case "CloudSpannerOperation.ExecuteStreamingQuery":
+ verifyRequestEvents(
+ spanItem,
+ expectedExecuteStreamingQueryEvents,
+ expectedExecuteStreamingQueryEventsCount);
+ break;
+ case "SessionPool.WaitForSession":
+ assertEquals(0, spanItem.getEvents().size());
+ break;
+ case "CloudSpanner.ReadOnlyTransaction":
+ verifyRequestEvents(
+ spanItem,
+ expectedReadOnlyTransactionMultiUseEvents,
+ expectedReadOnlyTransactionMultiUseEventsCount);
+ break;
+ default:
+ assert false;
+ }
+ });
+
+ verifySpans(actualSpanItems, expectedReadOnlyTransactionSpans);
+ }
+
+ @Test
+ public void transactionRunner() {
+ TransactionRunner runner = client.readWriteTransaction();
+ runner.run(transaction -> transaction.executeUpdate(UPDATE_STATEMENT));
+
+ List actualSpanItems = new ArrayList<>();
+ spanExporter
+ .getFinishedSpanItems()
+ .forEach(
+ spanItem -> {
+ actualSpanItems.add(spanItem.getName());
+ switch (spanItem.getName()) {
+ case "CloudSpannerOperation.BatchCreateSessionsRequest":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsRequestEvents,
+ expectedBatchCreateSessionsRequestEventsCount);
+ break;
+ case "CloudSpannerOperation.BatchCreateSessions":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsEvents,
+ expectedBatchCreateSessionsEventsCount);
+ break;
+ case "SessionPool.WaitForSession":
+ case "CloudSpannerOperation.Commit":
+ assertEquals(0, spanItem.getEvents().size());
+ break;
+ case "CloudSpanner.ReadWriteTransaction":
+ verifyRequestEvents(
+ spanItem,
+ expectedReadWriteTransactionEvents,
+ expectedReadWriteTransactionCount);
+ break;
+ default:
+ assert false;
+ }
+ });
+
+ verifySpans(actualSpanItems, expectedReadWriteTransactionWithCommitSpans);
+ }
+
+ @Test
+ public void transactionRunnerWithError() {
+ TransactionRunner runner = client.readWriteTransaction();
+ SpannerException e =
+ assertThrows(
+ SpannerException.class,
+ () -> runner.run(transaction -> transaction.executeUpdate(INVALID_UPDATE_STATEMENT)));
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+
+ List actualSpanItems = new ArrayList<>();
+ spanExporter
+ .getFinishedSpanItems()
+ .forEach(
+ spanItem -> {
+ actualSpanItems.add(spanItem.getName());
+ switch (spanItem.getName()) {
+ case "CloudSpannerOperation.BatchCreateSessionsRequest":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsRequestEvents,
+ expectedBatchCreateSessionsRequestEventsCount);
+ break;
+ case "CloudSpannerOperation.BatchCreateSessions":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsEvents,
+ expectedBatchCreateSessionsEventsCount);
+ break;
+ case "SessionPool.WaitForSession":
+ assertEquals(0, spanItem.getEvents().size());
+ break;
+ case "CloudSpanner.ReadWriteTransaction":
+ verifyRequestEvents(
+ spanItem,
+ expectedReadWriteTransactionErrorEvents,
+ expectedReadWriteTransactionErrorEventsCount);
+ break;
+ default:
+ assert false;
+ }
+ });
+
+ verifySpans(actualSpanItems, expectedReadWriteTransactionSpans);
+ }
+
+ @Test
+ public void transactionRunnerWithFailedAndBeginTransaction() {
+ Long updateCount =
+ client
+ .readWriteTransaction()
+ .run(
+ transaction -> {
+ // This update statement carries the BeginTransaction, but fails. This will
+ // cause the entire transaction to be retried with an explicit
+ // BeginTransaction RPC to ensure all statements in the transaction are
+ // actually executed against the same transaction.
+ SpannerException e =
+ assertThrows(
+ SpannerException.class,
+ () -> transaction.executeUpdate(INVALID_UPDATE_STATEMENT));
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+ return transaction.executeUpdate(UPDATE_STATEMENT);
+ });
+
+ List actualSpanItems = new ArrayList<>();
+ spanExporter
+ .getFinishedSpanItems()
+ .forEach(
+ spanItem -> {
+ actualSpanItems.add(spanItem.getName());
+ switch (spanItem.getName()) {
+ case "CloudSpannerOperation.BatchCreateSessionsRequest":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsRequestEvents,
+ expectedBatchCreateSessionsRequestEventsCount);
+ break;
+ case "CloudSpannerOperation.BatchCreateSessions":
+ verifyRequestEvents(
+ spanItem,
+ expectedBatchCreateSessionsEvents,
+ expectedBatchCreateSessionsEventsCount);
+ break;
+ case "SessionPool.WaitForSession":
+ case "CloudSpannerOperation.Commit":
+ case "CloudSpannerOperation.BeginTransaction":
+ assertEquals(0, spanItem.getEvents().size());
+ break;
+ case "CloudSpanner.ReadWriteTransaction":
+ verifyRequestEvents(
+ spanItem,
+ expectedReadWriteTransactionErrorWithBeginTransactionEvents,
+ expectedReadWriteTransactionErrorWithBeginTransactionEventsCount);
+ break;
+ default:
+ assert false;
+ }
+ });
+
+ verifySpans(actualSpanItems, expectedReadWriteTransactionWithCommitAndBeginTransactionSpans);
+ }
+
+ private void verifyRequestEvents(SpanData spanItem, List expectedEvents, int eventCount) {
+ List eventNames =
+ spanItem.getEvents().stream().map(EventData::getName).collect(Collectors.toList());
+ assertEquals(eventCount, spanItem.getEvents().size());
+ assertEquals(
+ eventNames.stream().distinct().sorted().collect(Collectors.toList()),
+ expectedEvents.stream().sorted().collect(Collectors.toList()));
+ }
+
+ private static void verifySpans(List actualSpanItems, List expectedSpansItems) {
+ assertEquals(
+ actualSpanItems.stream().distinct().sorted().collect(Collectors.toList()),
+ expectedSpansItems.stream().sorted().collect(Collectors.toList()));
+ }
+}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResumableStreamIteratorTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResumableStreamIteratorTest.java
index 06c1725d76a..217e818d42c 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResumableStreamIteratorTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResumableStreamIteratorTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.google.api.client.util.BackOff;
import com.google.cloud.spanner.AbstractResultSet.ResumableStreamIterator;
@@ -36,8 +37,10 @@
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.ProtoUtils;
-import io.opencensus.trace.EndSpanOptions;
import io.opencensus.trace.Span;
+import io.opencensus.trace.Tracing;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.context.Scope;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -132,15 +135,19 @@ public boolean isWithBeginTransaction() {
@Before
public void setUp() {
+ SpannerOptions.resetActiveTracingFramework();
+ SpannerOptions.enableOpenTelemetryTraces();
initWithLimit(Integer.MAX_VALUE);
}
private void initWithLimit(int maxBufferSize) {
+
resumableStreamIterator =
new AbstractResultSet.ResumableStreamIterator(
maxBufferSize,
"",
- null,
+ new OpenTelemetrySpan(mock(io.opentelemetry.api.trace.Span.class)),
+ new TraceWrapper(Tracing.getTracer(), OpenTelemetry.noop().getTracer("")),
SpannerStubSettings.newBuilder().executeStreamingSqlSettings().getRetrySettings(),
SpannerStubSettings.newBuilder().executeStreamingSqlSettings().getRetryableCodes()) {
@Override
@@ -163,12 +170,39 @@ public void simple() {
}
@Test
- public void closedSpan() {
+ public void closedOTSpan() {
+ SpannerOptions.resetActiveTracingFramework();
+ SpannerOptions.enableOpenTelemetryTraces();
Assume.assumeTrue(
"This test is only supported on JDK11 and lower",
JavaVersionUtil.getJavaMajorVersion() < 12);
- Span span = mock(Span.class);
+ io.opentelemetry.api.trace.Span oTspan = mock(io.opentelemetry.api.trace.Span.class);
+ ISpan span = new OpenTelemetrySpan(oTspan);
+ when(oTspan.makeCurrent()).thenReturn(mock(Scope.class));
+ setInternalState(ResumableStreamIterator.class, this.resumableStreamIterator, "span", span);
+
+ ResultSetStream s1 = Mockito.mock(ResultSetStream.class);
+ Mockito.when(starter.startStream(null)).thenReturn(new ResultSetIterator(s1));
+ Mockito.when(s1.next())
+ .thenReturn(resultSet(ByteString.copyFromUtf8("r1"), "a"))
+ .thenReturn(resultSet(ByteString.copyFromUtf8("r2"), "b"))
+ .thenReturn(null);
+ assertThat(consume(resumableStreamIterator)).containsExactly("a", "b").inOrder();
+
+ resumableStreamIterator.close("closed");
+ verify(oTspan).end();
+ }
+
+ @Test
+ public void closedOCSpan() {
+ SpannerOptions.resetActiveTracingFramework();
+ SpannerOptions.enableOpenCensusTraces();
+ Assume.assumeTrue(
+ "This test is only supported on JDK11 and lower",
+ JavaVersionUtil.getJavaMajorVersion() < 12);
+ Span mockSpan = mock(Span.class);
+ ISpan span = new OpenCensusSpan(mockSpan);
setInternalState(ResumableStreamIterator.class, this.resumableStreamIterator, "span", span);
ResultSetStream s1 = Mockito.mock(ResultSetStream.class);
@@ -180,7 +214,7 @@ public void closedSpan() {
assertThat(consume(resumableStreamIterator)).containsExactly("a", "b").inOrder();
resumableStreamIterator.close("closed");
- verify(span).end(EndSpanOptions.builder().setSampleToLocalSpanStore(true).build());
+ verify(mockSpan).end(OpenCensusSpan.END_SPAN_OPTIONS);
}
@Test
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionClientTests.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionClientTests.java
index 8af1323238a..c8d9bc39339 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionClientTests.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionClientTests.java
@@ -17,6 +17,8 @@
package com.google.cloud.spanner;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -27,6 +29,8 @@
import com.google.cloud.spanner.SessionClient.SessionConsumer;
import com.google.cloud.spanner.spi.v1.SpannerRpc;
import com.google.cloud.spanner.spi.v1.SpannerRpc.Option;
+import io.opencensus.trace.Tracing;
+import io.opentelemetry.api.OpenTelemetry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -83,6 +87,9 @@ public static Collection
@@ -62,47 +62,55 @@
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
proto-google-cloud-spanner-executor-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
grpc-google-cloud-spanner-executor-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.57.0
+ 6.58.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.57.0
+ 6.58.0
com.google.cloud
google-cloud-spanner
- 6.57.0
+ 6.58.0
+
+
+
+ io.opentelemetry
+ opentelemetry-bom
+ 1.34.1
+ pom
+ import
diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml
index ba2ef61db7b..afc8a9d036c 100644
--- a/proto-google-cloud-spanner-admin-database-v1/pom.xml
+++ b/proto-google-cloud-spanner-admin-database-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.57.0
+ 6.58.0
proto-google-cloud-spanner-admin-database-v1
PROTO library for proto-google-cloud-spanner-admin-database-v1
com.google.cloud
google-cloud-spanner-parent
- 6.57.0
+ 6.58.0
diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml
index 142ed3179ab..30c4451663c 100644
--- a/proto-google-cloud-spanner-admin-instance-v1/pom.xml
+++ b/proto-google-cloud-spanner-admin-instance-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.57.0
+ 6.58.0
proto-google-cloud-spanner-admin-instance-v1
PROTO library for proto-google-cloud-spanner-admin-instance-v1
com.google.cloud
google-cloud-spanner-parent
- 6.57.0
+ 6.58.0
diff --git a/proto-google-cloud-spanner-executor-v1/pom.xml b/proto-google-cloud-spanner-executor-v1/pom.xml
index ba0ff626523..4890cc004b3 100644
--- a/proto-google-cloud-spanner-executor-v1/pom.xml
+++ b/proto-google-cloud-spanner-executor-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-executor-v1
- 6.57.0
+ 6.58.0
proto-google-cloud-spanner-executor-v1
Proto library for google-cloud-spanner
com.google.cloud
google-cloud-spanner-parent
- 6.57.0
+ 6.58.0
diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml
index 922262b8344..0691dc941f4 100644
--- a/proto-google-cloud-spanner-v1/pom.xml
+++ b/proto-google-cloud-spanner-v1/pom.xml
@@ -4,13 +4,13 @@
4.0.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.57.0
+ 6.58.0
proto-google-cloud-spanner-v1
PROTO library for proto-google-cloud-spanner-v1
com.google.cloud
google-cloud-spanner-parent
- 6.57.0
+ 6.58.0
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index f518465160d..a17883b0acc 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -32,7 +32,7 @@
com.google.cloud
google-cloud-spanner
- 6.57.0
+ 6.58.0
diff --git a/samples/snippets/src/main/java/com/example/spanner/TracingSample.java b/samples/snippets/src/main/java/com/example/spanner/TracingSample.java
index 9dcd90d7705..a3d4e5bd072 100644
--- a/samples/snippets/src/main/java/com/example/spanner/TracingSample.java
+++ b/samples/snippets/src/main/java/com/example/spanner/TracingSample.java
@@ -32,7 +32,11 @@
import io.opencensus.trace.samplers.Samplers;
import java.util.Arrays;
-/** This sample demonstrates how to enable opencensus tracing and stats in cloud spanner client. */
+/** This sample demonstrates how to enable opencensus tracing and stats in cloud spanner client.
+ *
+ * @deprecated The OpenCensus project is deprecated. Use OpenTelemetry to enable metrics
+ * and stats with cloud spanner client.
+*/
public class TracingSample {
private static final String SAMPLE_SPAN = "CloudSpannerSample";
diff --git a/versions.txt b/versions.txt
index 5543d65ee61..5bfa2637503 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,13 +1,13 @@
# Format:
# module:released-version:current-version
-proto-google-cloud-spanner-admin-instance-v1:6.57.0:6.57.0
-proto-google-cloud-spanner-v1:6.57.0:6.57.0
-proto-google-cloud-spanner-admin-database-v1:6.57.0:6.57.0
-grpc-google-cloud-spanner-v1:6.57.0:6.57.0
-grpc-google-cloud-spanner-admin-instance-v1:6.57.0:6.57.0
-grpc-google-cloud-spanner-admin-database-v1:6.57.0:6.57.0
-google-cloud-spanner:6.57.0:6.57.0
-google-cloud-spanner-executor:6.57.0:6.57.0
-proto-google-cloud-spanner-executor-v1:6.57.0:6.57.0
-grpc-google-cloud-spanner-executor-v1:6.57.0:6.57.0
+proto-google-cloud-spanner-admin-instance-v1:6.58.0:6.58.0
+proto-google-cloud-spanner-v1:6.58.0:6.58.0
+proto-google-cloud-spanner-admin-database-v1:6.58.0:6.58.0
+grpc-google-cloud-spanner-v1:6.58.0:6.58.0
+grpc-google-cloud-spanner-admin-instance-v1:6.58.0:6.58.0
+grpc-google-cloud-spanner-admin-database-v1:6.58.0:6.58.0
+google-cloud-spanner:6.58.0:6.58.0
+google-cloud-spanner-executor:6.58.0:6.58.0
+proto-google-cloud-spanner-executor-v1:6.58.0:6.58.0
+grpc-google-cloud-spanner-executor-v1:6.58.0:6.58.0