From 3c1dd745d917eb196fdc590394f8ea75471243ef Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 10 Oct 2023 08:13:14 -0600 Subject: [PATCH 1/3] RUBY-3296 write to log when non-genuine host is detected --- lib/mongo/cluster.rb | 26 +++++++++++++++++++++++ lib/mongo/cluster/topology/base.rb | 16 +++++++++++++++ spec/mongo/cluster_spec.rb | 33 ++++++++++++++++++++++++++++++ spec/support/recording_logger.rb | 27 ++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 spec/support/recording_logger.rb diff --git a/lib/mongo/cluster.rb b/lib/mongo/cluster.rb index b5981c7471..509a66f2e3 100644 --- a/lib/mongo/cluster.rb +++ b/lib/mongo/cluster.rb @@ -186,6 +186,8 @@ def initialize(seeds, monitoring, options = Options::Redacted.new) recreate_topology(topology, opening_topology) end + possibly_warn_about_compatibility! + if load_balanced? # We are required by the specifications to produce certain SDAM events # when in load-balanced topology. @@ -1082,6 +1084,30 @@ def recreate_topology(new_topology_template, previous_topology) Monitoring::Event::TopologyChanged.new(previous_topology, @topology) ) end + + COSMOSDB_HOST_PATTERNS = %w[ .cosmos.azure.com ] + COSMOSDB_LOG_MESSAGE = 'You appear to be connected to a CosmosDB cluster. ' \ + 'For more information regarding feature compatibility and support please visit ' \ + 'https://www.mongodb.com/supportability/cosmosdb' + + DOCUMENTDB_HOST_PATTERNS = %w[ .docdb.amazonaws.com .docdb-elastic.amazonaws.com ] + DOCUMENTDB_LOG_MESSAGE = 'You appear to be connected to a DocumentDB cluster. ' \ + 'For more information regarding feature compatibility and support please visit ' \ + 'https://www.mongodb.com/supportability/documentdb' + + # Compares the server hosts with address suffixes of known services + # that provide limited MongoDB API compatibility, and warns about them. + def possibly_warn_about_compatibility! + if topology.server_hosts_match_any?(COSMOSDB_HOST_PATTERNS) + log_info COSMOSDB_LOG_MESSAGE + return + end + + if topology.server_hosts_match_any?(DOCUMENTDB_HOST_PATTERNS) + log_info DOCUMENTDB_LOG_MESSAGE + return + end + end end end diff --git a/lib/mongo/cluster/topology/base.rb b/lib/mongo/cluster/topology/base.rb index e92181e4fa..999d14bf75 100644 --- a/lib/mongo/cluster/topology/base.rb +++ b/lib/mongo/cluster/topology/base.rb @@ -211,6 +211,22 @@ def new_max_set_version(description) end end + # Compares each server address against the list of patterns. + # + # @param [ Array ] patterns the URL suffixes to compare + # each server against. + # + # @return [ true | false ] whether any of the addresses match any of + # the patterns or not. + # + # @api private + def server_hosts_match_any?(patterns) + server_descriptions.any? do |addr_spec, _desc| + addr, _port = addr_spec.split(/:/) + patterns.any? { |pattern| addr.end_with?(pattern) } + end + end + private # Validates and/or transforms options as necessary for the topology. diff --git a/spec/mongo/cluster_spec.rb b/spec/mongo/cluster_spec.rb index b93f529a0e..df9ec93a6b 100644 --- a/spec/mongo/cluster_spec.rb +++ b/spec/mongo/cluster_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'spec_helper' +require 'support/recording_logger' # let these existing styles stand, rather than going in for a deep refactoring # of these specs. @@ -84,6 +85,38 @@ ) end end + + context 'when a non-genuine host is detected' do + let(:logger) { RecordingLogger.new } + before { described_class.new(host_names, monitoring, logger: logger, monitoring_io: false) } + + shared_examples 'an action that logs' do + it 'writes a warning to the log' do + expect(logger.lines).to include(a_string_matching(expected_log_output)) + end + end + + context 'when CosmosDB is detected' do + let(:host_names) { %w[ xyz.cosmos.azure.com ] } + let(:expected_log_output) { /https:\/\/www.mongodb.com\/supportability\/cosmosdb/ } + + it_behaves_like 'an action that logs' + end + + context 'when DocumentDB is detected' do + let(:expected_log_output) { /https:\/\/www.mongodb.com\/supportability\/documentdb/ } + + context 'with docdb uri' do + let(:host_names) { [ 'xyz.docdb.amazonaws.com' ] } + it_behaves_like 'an action that logs' + end + + context 'with docdb-elastic uri' do + let(:host_names) { [ 'xyz.docdb-elastic.amazonaws.com' ] } + it_behaves_like 'an action that logs' + end + end + end end describe '#==' do diff --git a/spec/support/recording_logger.rb b/spec/support/recording_logger.rb new file mode 100644 index 0000000000..cfed9b0f28 --- /dev/null +++ b/spec/support/recording_logger.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +# rubocop:todo all + +require 'stringio' + +# A "Logger-alike" class, quacking like ::Logger, used for recording messages +# as they are written to the log +class RecordingLogger < Logger + def initialize(*args, **kwargs) + @buffer = StringIO.new + super(@buffer, *args, **kwargs) + end + + # Accesses the raw contents of the log + # + # @return [ String ] the raw contents of the log + def contents + @buffer.string + end + + # Returns the contents of the log as individual lines. + # + # @return [ Array ] the individual log lines + def lines + contents.split(/\n/) + end +end From 92612498d64396b695c99fe95413399462fd3711 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 10 Oct 2023 08:21:00 -0600 Subject: [PATCH 2/3] rubocop appeasement --- spec/mongo/client_construction_spec.rb | 4 ++-- spec/mongo/cluster_spec.rb | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index 442c0480b3..561ad4efda 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -2659,11 +2659,11 @@ new_client.cluster.next_primary # Diagnostics - # rubocop:disable Style/IfUnlessModifier, Lint/Debugger + # rubocop:disable Style/IfUnlessModifier unless subscriber.started_events.empty? p subscriber.started_events end - # rubocop:enable Style/IfUnlessModifier, Lint/Debugger + # rubocop:enable Style/IfUnlessModifier expect(subscriber.started_events.length).to eq 0 expect(new_client.cluster.topology.class).not_to be Mongo::Cluster::Topology::Unknown diff --git a/spec/mongo/cluster_spec.rb b/spec/mongo/cluster_spec.rb index df9ec93a6b..b87733f261 100644 --- a/spec/mongo/cluster_spec.rb +++ b/spec/mongo/cluster_spec.rb @@ -87,9 +87,10 @@ end context 'when a non-genuine host is detected' do - let(:logger) { RecordingLogger.new } before { described_class.new(host_names, monitoring, logger: logger, monitoring_io: false) } + let(:logger) { RecordingLogger.new } + shared_examples 'an action that logs' do it 'writes a warning to the log' do expect(logger.lines).to include(a_string_matching(expected_log_output)) @@ -98,21 +99,23 @@ context 'when CosmosDB is detected' do let(:host_names) { %w[ xyz.cosmos.azure.com ] } - let(:expected_log_output) { /https:\/\/www.mongodb.com\/supportability\/cosmosdb/ } + let(:expected_log_output) { %r{https://www.mongodb.com/supportability/cosmosdb} } it_behaves_like 'an action that logs' end context 'when DocumentDB is detected' do - let(:expected_log_output) { /https:\/\/www.mongodb.com\/supportability\/documentdb/ } + let(:expected_log_output) { %r{https://www.mongodb.com/supportability/documentdb} } context 'with docdb uri' do let(:host_names) { [ 'xyz.docdb.amazonaws.com' ] } + it_behaves_like 'an action that logs' end context 'with docdb-elastic uri' do let(:host_names) { [ 'xyz.docdb-elastic.amazonaws.com' ] } + it_behaves_like 'an action that logs' end end From 8c8d478cd2230113d85166872cada2d77f6f0f91 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 10 Oct 2023 08:35:30 -0600 Subject: [PATCH 3/3] apparently the Lint/Debugger rubocop is needed in CI, but not locally? --- spec/mongo/client_construction_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index 561ad4efda..442c0480b3 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -2659,11 +2659,11 @@ new_client.cluster.next_primary # Diagnostics - # rubocop:disable Style/IfUnlessModifier + # rubocop:disable Style/IfUnlessModifier, Lint/Debugger unless subscriber.started_events.empty? p subscriber.started_events end - # rubocop:enable Style/IfUnlessModifier + # rubocop:enable Style/IfUnlessModifier, Lint/Debugger expect(subscriber.started_events.length).to eq 0 expect(new_client.cluster.topology.class).not_to be Mongo::Cluster::Topology::Unknown