From ade2581a110c8c9f90293d9875ba09e39e795128 Mon Sep 17 00:00:00 2001 From: Jiren Patel Date: Thu, 13 Feb 2020 09:07:09 +0530 Subject: [PATCH] feat(spanner): allow custom lib name and version for telemetry purpose (#4762) * allow custom lib name and version for telemetry purpose * format docs * added custom lib name and version prefix - updated doc and tests * update test cases for lib name and version --- .../lib/google-cloud-spanner.rb | 43 ++++- .../lib/google/cloud/spanner.rb | 31 +++- .../lib/google/cloud/spanner/service.rb | 21 ++- .../test/google/cloud/spanner_test.rb | 162 +++++++++++++++++- 4 files changed, 240 insertions(+), 17 deletions(-) diff --git a/google-cloud-spanner/lib/google-cloud-spanner.rb b/google-cloud-spanner/lib/google-cloud-spanner.rb index 5c26fd2166c3..40dbc926c7ee 100644 --- a/google-cloud-spanner/lib/google-cloud-spanner.rb +++ b/google-cloud-spanner/lib/google-cloud-spanner.rb @@ -45,6 +45,20 @@ module Cloud # @param [Integer] timeout Default timeout to use in requests. Optional. # @param [Hash] client_config A hash of values to override the default # behavior of the API client. Optional. + # @param [String] lib_name Library name. This will be added as a prefix + # to the API call tracking header `x-goog-api-client` with provided + # lib version for telemetry. Optional. For example prefix looks like + # `spanner-activerecord/0.0.1 gccl/1.13.1`. Here, + # `spanner-activerecord/0.0.1` is provided custom library name and + # version and `gccl/1.13.1` represents the Cloud Spanner Ruby library + # with version. + # @param [String] lib_version Library version. This will be added as a + # prefix to the API call tracking header `x-goog-api-client` with + # provided lib name for telemetry. Optional. For example prefix look like + # `spanner-activerecord/0.0.1 gccl/1.13.1`. Here, + # `spanner-activerecord/0.0.1` is provided custom library name and + # version and `gccl/1.13.1` represents the Cloud Spanner Ruby library + # with version. # # @return [Google::Cloud::Spanner::Project] # @@ -61,10 +75,13 @@ module Cloud # platform_scope = "https://www.googleapis.com/auth/cloud-platform" # spanner = gcloud.spanner scope: platform_scope # - def spanner scope: nil, timeout: nil, client_config: nil + def spanner scope: nil, timeout: nil, client_config: nil, lib_name: nil, + lib_version: nil Google::Cloud.spanner @project, @keyfile, scope: scope, timeout: (timeout || @timeout), - client_config: client_config + client_config: client_config, + lib_name: lib_name, + lib_version: lib_version end ## @@ -92,6 +109,20 @@ def spanner scope: nil, timeout: nil, client_config: nil # @param [Integer] timeout Default timeout to use in requests. Optional. # @param [Hash] client_config A hash of values to override the default # behavior of the API client. Optional. + # @param [String] lib_name Library name. This will be added as a prefix + # to the API call tracking header `x-goog-api-client` with provided + # lib version for telemetry. Optional. For example prefix looks like + # `spanner-activerecord/0.0.1 gccl/1.13.1`. Here, + # `spanner-activerecord/0.0.1` is provided custom library name and + # version and `gccl/1.13.1` represents the Cloud Spanner Ruby library + # with version. + # @param [String] lib_version Library version. This will be added as a + # prefix to the API call tracking header `x-goog-api-client` with + # provided lib name for telemetry. Optional. For example prefix look like + # `spanner-activerecord/0.0.1 gccl/1.13.1`. Here, + # `spanner-activerecord/0.0.1` is provided custom library name and + # version and `gccl/1.13.1` represents the Cloud Spanner Ruby library + # with version. # # @return [Google::Cloud::Spanner::Project] # @@ -101,12 +132,14 @@ def spanner scope: nil, timeout: nil, client_config: nil # spanner = Google::Cloud.spanner # def self.spanner project_id = nil, credentials = nil, scope: nil, - timeout: nil, client_config: nil + timeout: nil, client_config: nil, lib_name: nil, + lib_version: nil require "google/cloud/spanner" Google::Cloud::Spanner.new project_id: project_id, credentials: credentials, scope: scope, timeout: timeout, - client_config: client_config + client_config: client_config, + lib_name: lib_name, lib_version: lib_version end end end @@ -137,4 +170,6 @@ def self.spanner project_id = nil, credentials = nil, scope: nil, config.add_field! :client_config, nil, match: Hash config.add_field! :endpoint, nil, match: String config.add_field! :emulator_host, default_emulator, match: String, allow_nil: true + config.add_field! :lib_name, nil, match: String, allow_nil: true + config.add_field! :lib_version, nil, match: String, allow_nil: true end diff --git a/google-cloud-spanner/lib/google/cloud/spanner.rb b/google-cloud-spanner/lib/google/cloud/spanner.rb index da4241334b0b..f6405aeb40b4 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner.rb @@ -34,7 +34,7 @@ module Cloud # See {file:OVERVIEW.md Spanner Overview}. # module Spanner - # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/MethodLength,Metrics/AbcSize ## # Creates a new object for connecting to the Spanner service. @@ -68,6 +68,20 @@ module Spanner # Deprecated. # @param [String] emulator_host Spanner emulator host. Optional. # If the param is nil, uses the value of the `emulator_host` config. + # @param [String] lib_name Library name. This will be added as a prefix + # to the API call tracking header `x-goog-api-client` with provided + # lib version for telemetry. Optional. For example prefix looks like + # `spanner-activerecord/0.0.1 gccl/1.13.1`. Here, + # `spanner-activerecord/0.0.1` is provided custom library name and + # version and `gccl/1.13.1` represents the Cloud Spanner Ruby library + # with version. + # @param [String] lib_version Library version. This will be added as a + # prefix to the API call tracking header `x-goog-api-client` with + # provided lib name for telemetry. Optional. For example prefix look like + # `spanner-activerecord/0.0.1 gccl/1.13.1`. Here, + # `spanner-activerecord/0.0.1` is provided custom library name and + # version and `gccl/1.13.1` represents the Cloud Spanner Ruby library + # with version. # # @return [Google::Cloud::Spanner::Project] # @@ -78,7 +92,7 @@ module Spanner # def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, client_config: nil, endpoint: nil, project: nil, keyfile: nil, - emulator_host: nil + emulator_host: nil, lib_name: nil, lib_version: nil project_id ||= (project || default_project_id) scope ||= configure.scope timeout ||= configure.timeout @@ -86,6 +100,8 @@ def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, endpoint ||= configure.endpoint credentials ||= (keyfile || default_credentials(scope: scope)) emulator_host ||= configure.emulator_host + lib_name ||= configure.lib_name + lib_version ||= configure.lib_version if emulator_host credentials = :this_channel_is_insecure @@ -106,12 +122,13 @@ def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, Spanner::Project.new( Spanner::Service.new( project_id, credentials, - host: endpoint, timeout: timeout, client_config: client_config + host: endpoint, timeout: timeout, client_config: client_config, + lib_name: lib_name, lib_version: lib_version ) ) end - # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/MethodLength,Metrics/AbcSize ## # Configure the Google Cloud Spanner library. @@ -133,6 +150,12 @@ def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, # to use the default endpoint. # * `emulator_host` - (String) Host name of the emulator. Defaults to # `ENV["SPANNER_EMULATOR_HOST"]`. + # * `lib_name` - (String) Override the lib name , or `nil` + # to use the default lib name without prefix in agent tracking + # header. + # * `lib_version` - (String) Override the lib version , or `nil` + # to use the default version lib name without prefix in agent + # tracking header. # # @return [Google::Cloud::Config] The configuration object the # Google::Cloud::Spanner library uses. diff --git a/google-cloud-spanner/lib/google/cloud/spanner/service.rb b/google-cloud-spanner/lib/google/cloud/spanner/service.rb index 1f758e370b3d..80381ce0bc0c 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/service.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/service.rb @@ -29,17 +29,20 @@ module Spanner # @private Represents the gRPC Spanner service, including all the API # methods. class Service - attr_accessor :project, :credentials, :timeout, :client_config, :host + attr_accessor :project, :credentials, :timeout, :client_config, :host, + :lib_name, :lib_version ## # Creates a new Service instance. def initialize project, credentials, host: nil, timeout: nil, - client_config: nil + client_config: nil, lib_name: nil, lib_version: nil @project = project @credentials = credentials @host = host || V1::SpannerClient::SERVICE_ADDRESS @timeout = timeout @client_config = client_config || {} + @lib_name = lib_name + @lib_version = lib_version end def channel @@ -67,7 +70,7 @@ def service client_config: client_config, service_address: service_address, service_port: service_port, - lib_name: "gccl", + lib_name: lib_name_with_prefix, lib_version: Google::Cloud::Spanner::VERSION ) end @@ -82,7 +85,7 @@ def instances client_config: client_config, service_address: service_address, service_port: service_port, - lib_name: "gccl", + lib_name: lib_name_with_prefix, lib_version: Google::Cloud::Spanner::VERSION ) end @@ -97,7 +100,7 @@ def databases client_config: client_config, service_address: service_address, service_port: service_port, - lib_name: "gccl", + lib_name: lib_name_with_prefix, lib_version: Google::Cloud::Spanner::VERSION ) end @@ -459,6 +462,14 @@ def service_port URI.parse("//#{host}").port end + def lib_name_with_prefix + return "gccl" if [nil, "gccl"].include? lib_name + + value = lib_name.dup + value << "/#{lib_version}" if lib_version + value << " gccl" + end + def default_options_from_session session_name default_prefix = session_name.split("/sessions/").first Google::Gax::CallOptions.new kwargs: \ diff --git a/google-cloud-spanner/test/google/cloud/spanner_test.rb b/google-cloud-spanner/test/google/cloud/spanner_test.rb index 77433c2e1fcd..c3c33fa71a28 100644 --- a/google-cloud-spanner/test/google/cloud/spanner_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner_test.rb @@ -18,13 +18,15 @@ describe "#spanner" do it "calls out to Google::Cloud.spanner" do gcloud = Google::Cloud.new - stubbed_spanner = ->(project, keyfile, scope: nil, timeout: nil, host: nil, client_config: nil) { + stubbed_spanner = ->(project, keyfile, scope: nil, timeout: nil, host: nil, client_config: nil, lib_name: nil, lib_version: nil) { project.must_be :nil? keyfile.must_be :nil? scope.must_be :nil? timeout.must_be :nil? host.must_be :nil? client_config.must_be :nil? + lib_name.must_be :nil? + lib_version.must_be :nil? "spanner-project-object-empty" } Google::Cloud.stub :spanner, stubbed_spanner do @@ -35,13 +37,15 @@ it "passes project and keyfile to Google::Cloud.spanner" do gcloud = Google::Cloud.new "project-id", "keyfile-path" - stubbed_spanner = ->(project, keyfile, scope: nil, timeout: nil, host: nil, client_config: nil) { + stubbed_spanner = ->(project, keyfile, scope: nil, timeout: nil, host: nil, client_config: nil, lib_name: nil, lib_version: nil) { project.must_equal "project-id" keyfile.must_equal "keyfile-path" scope.must_be :nil? timeout.must_be :nil? host.must_be :nil? client_config.must_be :nil? + lib_name.must_be :nil? + lib_version.must_be :nil? "spanner-project-object" } Google::Cloud.stub :spanner, stubbed_spanner do @@ -52,13 +56,15 @@ it "passes project and keyfile and options to Google::Cloud.spanner" do gcloud = Google::Cloud.new "project-id", "keyfile-path" - stubbed_spanner = ->(project, keyfile, scope: nil, timeout: nil, host: nil, client_config: nil) { + stubbed_spanner = ->(project, keyfile, scope: nil, timeout: nil, host: nil, client_config: nil, lib_name: nil, lib_version: nil) { project.must_equal "project-id" keyfile.must_equal "keyfile-path" scope.must_equal "http://example.com/scope" timeout.must_equal 60 host.must_be :nil? client_config.must_equal({ "gax" => "options" }) + lib_name.must_be :nil? + lib_version.must_be :nil? "spanner-project-object-scoped" } Google::Cloud.stub :spanner, stubbed_spanner do @@ -66,6 +72,25 @@ project.must_equal "spanner-project-object-scoped" end end + + it "passes lib name and version to Google::Cloud.spanner" do + gcloud = Google::Cloud.new + stubbed_spanner = ->(project, keyfile, scope: nil, timeout: nil, host: nil, client_config: nil, lib_name: nil, lib_version: nil) { + project.must_be :nil? + keyfile.must_be :nil? + scope.must_be :nil? + timeout.must_be :nil? + host.must_be :nil? + client_config.must_be :nil? + lib_name.must_equal "spanner-ruby" + lib_version.must_equal "1.0.0" + "spanner-project-object-with-lib-version-name" + } + Google::Cloud.stub :spanner, stubbed_spanner do + project = gcloud.spanner lib_name: "spanner-ruby", lib_version: "1.0.0" + project.must_equal "spanner-project-object-with-lib-version-name" + end + end end describe ".spanner" do @@ -100,11 +125,16 @@ def creds.is_a? target "spanner-credentials" } stubbed_service = ->(project, credentials, timeout: nil, host: nil, client_config: nil, **keyword_args) { + project.must_equal "project-id" credentials.must_equal "spanner-credentials" timeout.must_be :nil? host.must_be :nil? client_config.must_be :nil? + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -136,7 +166,7 @@ def creds.is_a? target end let(:found_credentials) { "{}" } - it "gets defaults for project_id and keyfile" do + it "gets defaults for project_id, keyfile, lib_name and lib_version" do # Clear all environment variables ENV.stub :[], nil do # Get project_id from Google Compute Engine @@ -146,6 +176,9 @@ def creds.is_a? target spanner.must_be_kind_of Google::Cloud::Spanner::Project spanner.project.must_equal "project-id" spanner.service.credentials.must_equal default_credentials + spanner.service.lib_name.must_be :nil? + spanner.service.lib_version.must_be :nil? + spanner.service.send(:lib_name_with_prefix).must_equal "gccl" end end end @@ -163,6 +196,10 @@ def creds.is_a? target timeout.must_be :nil? host.must_be :nil? client_config.must_be :nil? + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -191,6 +228,10 @@ def creds.is_a? target timeout.must_be :nil? host.must_equal endpoint client_config.must_be :nil? + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -217,6 +258,10 @@ def creds.is_a? target timeout.must_be :nil? host.must_be :nil? client_config.must_be :nil? + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -250,6 +295,10 @@ def creds.is_a? target timeout.must_be :nil? host.must_be :nil? client_config.must_be :nil? + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } empty_env = OpenStruct.new @@ -307,6 +356,45 @@ def creds.is_a? target end end end + + it "uses provided lib name and lib version" do + lib_name = "spanner-ruby" + lib_version = "1.0.0" + + # Clear all environment variables + ENV.stub :[], nil do + # Get project_id from Google Compute Engine + Google::Cloud.stub :env, OpenStruct.new(project_id: "project-id") do + Google::Cloud::Spanner::Credentials.stub :default, default_credentials do + spanner = Google::Cloud::Spanner.new lib_name: lib_name, lib_version: lib_version + spanner.must_be_kind_of Google::Cloud::Spanner::Project + spanner.project.must_equal "project-id" + spanner.service.lib_name.must_equal lib_name + spanner.service.lib_version.must_equal lib_version + spanner.service.send(:lib_name_with_prefix).must_equal "#{lib_name}/#{lib_version} gccl" + end + end + end + end + + it "uses provided lib name only" do + lib_name = "spanner-ruby" + + # Clear all environment variables + ENV.stub :[], nil do + # Get project_id from Google Compute Engine + Google::Cloud.stub :env, OpenStruct.new(project_id: "project-id") do + Google::Cloud::Spanner::Credentials.stub :default, default_credentials do + spanner = Google::Cloud::Spanner.new lib_name: lib_name + spanner.must_be_kind_of Google::Cloud::Spanner::Project + spanner.project.must_equal "project-id" + spanner.service.lib_name.must_equal lib_name + spanner.service.lib_version.must_be :nil? + spanner.service.send(:lib_name_with_prefix).must_equal "#{lib_name} gccl" + end + end + end + end end describe "Spanner.configure" do @@ -333,6 +421,10 @@ def creds.is_a? target timeout.must_be :nil? host.must_be :nil? client_config.must_be :nil? + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -371,6 +463,10 @@ def creds.is_a? target timeout.must_be :nil? host.must_be :nil? client_config.must_be :nil? + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -409,6 +505,10 @@ def creds.is_a? target timeout.must_equal 42 host.must_be :nil? client_config.must_equal spanner_client_config + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -449,6 +549,10 @@ def creds.is_a? target timeout.must_equal 42 host.must_be :nil? client_config.must_equal spanner_client_config + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -490,6 +594,10 @@ def creds.is_a? target timeout.must_be :nil? host.must_equal endpoint client_config.must_be :nil? + keyword_args.key?(:lib_name).must_equal true + keyword_args.key?(:lib_version).must_equal true + keyword_args[:lib_name].must_be :nil? + keyword_args[:lib_version].must_be :nil? OpenStruct.new project: project } @@ -534,5 +642,51 @@ def creds.is_a? target spanner.service.host.must_equal "localhost:4567" end end + + it "uses spanner config for custom lib name and version" do + custom_lib_name = "spanner-ruby" + custom_lib_version = "1.0.0" + + stubbed_credentials = ->(keyfile, scope: nil) { + scope.must_be :nil? + "spanner-credentials" + } + stubbed_service = ->(project, credentials, timeout: nil, host: nil, client_config: nil, **keyword_args) { + project.must_equal "project-id" + credentials.must_equal "spanner-credentials" + timeout.must_be :nil? + host.must_be :nil? + client_config.must_be :nil? + keyword_args[:lib_name].must_equal custom_lib_name + keyword_args[:lib_version].must_equal custom_lib_version + OpenStruct.new project: project, lib_name: keyword_args[:lib_name], lib_version: keyword_args[:lib_version] + } + + # Clear all environment variables + ENV.stub :[], nil do + # Set new configuration + Google::Cloud::Spanner.configure do |config| + config.project = "project-id" + config.keyfile = "path/to/keyfile.json" + config.lib_name = custom_lib_name + config.lib_version = custom_lib_version + end + + File.stub :file?, true, ["path/to/keyfile.json"] do + File.stub :read, found_credentials, ["path/to/keyfile.json"] do + Google::Cloud::Spanner::Credentials.stub :new, stubbed_credentials do + Google::Cloud::Spanner::Service.stub :new, stubbed_service do + spanner = Google::Cloud::Spanner.new + spanner.must_be_kind_of Google::Cloud::Spanner::Project + spanner.project.must_equal "project-id" + spanner.service.must_be_kind_of OpenStruct + spanner.service.lib_name.must_equal custom_lib_name + spanner.service.lib_version.must_equal custom_lib_version + end + end + end + end + end + end end end