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..b87733f261 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,41 @@ ) end end + + context 'when a non-genuine host is detected' do + 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)) + end + end + + context 'when CosmosDB is detected' do + let(:host_names) { %w[ xyz.cosmos.azure.com ] } + 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) { %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 + 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