1# Copyright 2015 - present MongoDB, Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15use strict; 16use warnings; 17use Test::More 0.96; 18use Test::Fatal; 19use JSON::MaybeXS; 20use Path::Tiny 0.054; # basename with suffix 21 22use MongoDB; 23use MongoDB::ReadPreference; 24use MongoDB::_Credential; 25use MongoDB::_Server; 26use MongoDB::_Topology; 27use MongoDB::_URI; 28 29subtest "rtt tests" => sub { 30 my $iterator = path('t/data/SS/rtt')->iterator( { recurse => 1 } ); 31 32 for my $path ( exhaust_sort($iterator) ) { 33 next unless -f $path && $path =~ /\.json$/; 34 my $plan = eval { decode_json( $path->slurp_utf8 ) }; 35 if ($@) { 36 die "Error decoding $path: $@"; 37 } 38 run_rtt_test( $path->basename(".json"), $plan ); 39 } 40 41 like( 42 exception { create_mock_server( "localhost:27017", -1 ) }, 43 qr/non-negative number/, 44 "negative RTT times throw an excepton" 45 ); 46}; 47 48subtest "server selection tests" => sub { 49 my $source = path('t/data/SS/server_selection'); 50 my $iterator = $source->iterator( { recurse => 1 } ); 51 52 for my $path ( exhaust_sort($iterator) ) { 53 next unless -f $path && $path =~ /\.json$/; 54 my $plan = eval { decode_json( $path->slurp_utf8 ) }; 55 if ($@) { 56 die "Error decoding $path: $@"; 57 } 58 run_ss_test( $path->relative($source), $plan ); 59 } 60}; 61 62subtest "random selection" => sub { 63 64 my $topo = create_mock_topology( "mongodb://localhost", { type => 'Sharded' } ); 65 $topo->_remove_address("localhost:27017"); 66 67 for my $n ( "a" .. "z" ) { 68 my $address = "$n:27017"; 69 my $server = create_mock_server( $address, 10, type => 'Mongos' ); 70 $topo->servers->{$server->address} = $server; 71 $topo->_update_ewma( $server->address, $server ); 72 } 73 74 # try up to 20 75 my $first = $topo->_find_available_server; 76 77 my $different = 0; 78 for ( 1 .. 20 ) { 79 my $another = $topo->_find_available_server; 80 if ( $first->address ne $another->address ) { 81 $different = 1; 82 last; 83 } 84 } 85 86 ok( $different, "servers randomly selected" ); 87}; 88 89subtest "server_selector" => sub { 90 91 my $topo = create_mock_topology( 92 "mongodb://localhost", { type => 'Sharded', server_selector => sub { shift } } 93 ); 94 $topo->_remove_address("localhost:27017"); 95 96 for my $n ( "a" .. "z" ) { 97 my $address = "$n:27017"; 98 my $server = create_mock_server( $address, 10, type => 'Mongos' ); 99 $topo->servers->{$server->address} = $server; 100 $topo->_update_ewma( $server->address, $server ); 101 } 102 103 my $first = $topo->_find_available_server; 104 for ( 1 .. 5 ) { 105 my $another = $topo->_find_available_server; 106 is($first->address, $another->address, 107 'same server always, since server_selector is set' 108 ); 109 } 110}; 111 112sub exhaust_sort { 113 my $iter = shift; 114 my @result; 115 while ( defined( my $i = $iter->() ) ) { 116 push @result, $i; 117 } 118 return sort @result; 119} 120 121sub create_mock_topology { 122 my ( $uri, $options ) = @_; 123 $options->{'type'} ||= 'Single'; 124 125 return MongoDB::_Topology->new( 126 uri => MongoDB::_URI->new( uri => $uri ), 127 min_server_version => "0.0.0", 128 max_wire_version => 3, 129 min_wire_version => 0, 130 heartbeat_frequency_ms => 3600000, 131 last_scan_time => time + 60, 132 credential => MongoDB::_Credential->new( 133 mechanism => 'NONE', 134 monitoring_callback => undef, 135 ), 136 monitoring_callback => undef, 137 %$options, 138 ); 139} 140 141sub create_mock_server { 142 my ( $address, $rtt, @args ) = @_; 143 return MongoDB::_Server->new( 144 address => $address, 145 last_update_time => 0, 146 rtt_sec => $rtt, 147 is_master => { ismaster => 1, ok => 1 }, 148 @args, 149 ); 150} 151 152sub run_rtt_test { 153 my ( $name, $plan ) = @_; 154 155 my $topo = create_mock_topology("mongodb://localhost"); 156 157 if ( $plan->{avg_rtt_ms} ne 'NULL' ) { 158 $topo->rtt_ewma_sec->{"localhost:27017"} = $plan->{avg_rtt_ms}/1000; 159 } 160 161 my $server = create_mock_server( "localhost:2707", $plan->{new_rtt_ms}/1000 ); 162 163 $topo->_update_topology_from_server_desc( 'localhost:27017', $server ); 164 165 is( $topo->rtt_ewma_sec->{"localhost:27017"}, $plan->{new_avg_rtt}/1000, $name ); 166} 167 168sub run_ss_test { 169 my ( $name, $plan ) = @_; 170 171 $name =~ s{\.json$}{}; 172 173 my $topo_desc = $plan->{topology_description}; 174 my $topo = create_mock_topology( "mongodb://localhost", { type => $topo_desc->{type} } ); 175 $topo->_remove_address("localhost:27017"); 176 for my $s ( @{ $topo_desc->{servers} } ) { 177 my $address = $s->{address}; 178 my $server = create_mock_server( 179 $address, 180 $s->{avg_rtt_ms}/1000, 181 type => $s->{type}, 182 tags => $s->{tags} || {}, 183 ); 184 $topo->servers->{$server->address} = $server; 185 $topo->_update_ewma( $server->address, $server ); 186 } 187 188 my $got; 189 if ( $plan->{operation} eq 'read' ) { 190 my $read_pref = MongoDB::ReadPreference->new( 191 mode => $plan->{read_preference}{mode}, 192 tag_sets => $plan->{read_preference}{tag_sets} || {}, 193 ); 194 my $mode = $read_pref ? lc $read_pref->mode : 'primary'; 195 my $method = 196 $topo->type eq "Single" ? '_find_available_server' 197 : $topo->type eq "Sharded" ? '_find_readable_mongos_server' 198 : "_find_${mode}_server"; 199 200 $got = $topo->$method($read_pref); 201 } 202 else { 203 my $method = 204 $topo->type eq 'Single' || $topo->type eq 'Sharded' 205 ? '_find_available_server' 206 : "_find_primary_server"; 207 208 $got = $topo->$method; 209 } 210 211 if ( my @expect = @{ $plan->{in_latency_window} } ) { 212 if ( defined($got) ) { 213 my $got_address = $got->address; 214 my $found = grep { $got_address eq $_->{address} } @expect; 215 ok( $found, $name ); 216 } 217 else { 218 fail( "expected @expect, but got nothing" ); 219 } 220 } 221 else { 222 ok( !defined($got), $name ); 223 } 224} 225 226done_testing; 227