storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/http/dial_dnscache_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package http
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"math/rand"
    24  	"net"
    25  	"testing"
    26  	"time"
    27  )
    28  
    29  var (
    30  	testFreq                 = 1 * time.Second
    31  	testDefaultLookupTimeout = 1 * time.Second
    32  )
    33  
    34  func logOnce(ctx context.Context, err error, id interface{}, errKind ...interface{}) {
    35  	// no-op
    36  }
    37  
    38  func testDNSCache(t *testing.T) *DNSCache {
    39  	t.Helper() // skip printing file and line information from this function
    40  	return NewDNSCache(testFreq, testDefaultLookupTimeout, logOnce)
    41  }
    42  
    43  func TestDialContextWithDNSCache(t *testing.T) {
    44  	resolver := &DNSCache{
    45  		cache: map[string][]string{
    46  			"play.min.io": {
    47  				"127.0.0.1",
    48  				"127.0.0.2",
    49  				"127.0.0.3",
    50  			},
    51  		},
    52  	}
    53  
    54  	cases := []struct {
    55  		permF func(n int) []int
    56  		dialF DialContext
    57  	}{
    58  		{
    59  			permF: func(n int) []int {
    60  				return []int{0}
    61  			},
    62  			dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
    63  				if got, want := addr, net.JoinHostPort("127.0.0.1", "443"); got != want {
    64  					t.Fatalf("got addr %q, want %q", got, want)
    65  				}
    66  				return nil, nil
    67  			},
    68  		},
    69  		{
    70  			permF: func(n int) []int {
    71  				return []int{1}
    72  			},
    73  			dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
    74  				if got, want := addr, net.JoinHostPort("127.0.0.2", "443"); got != want {
    75  					t.Fatalf("got addr %q, want %q", got, want)
    76  				}
    77  				return nil, nil
    78  			},
    79  		},
    80  		{
    81  			permF: func(n int) []int {
    82  				return []int{2}
    83  			},
    84  			dialF: func(ctx context.Context, network, addr string) (net.Conn, error) {
    85  				if got, want := addr, net.JoinHostPort("127.0.0.3", "443"); got != want {
    86  					t.Fatalf("got addr %q, want %q", got, want)
    87  				}
    88  				return nil, nil
    89  			},
    90  		},
    91  	}
    92  
    93  	origFunc := randPerm
    94  	defer func() {
    95  		randPerm = origFunc
    96  	}()
    97  
    98  	for _, tc := range cases {
    99  		t.Run("", func(t *testing.T) {
   100  			randPerm = tc.permF
   101  			if _, err := DialContextWithDNSCache(resolver, tc.dialF)(context.Background(), "tcp", "play.min.io:443"); err != nil {
   102  				t.Fatalf("err: %s", err)
   103  			}
   104  		})
   105  	}
   106  
   107  }
   108  
   109  func TestDialContextWithDNSCacheRand(t *testing.T) {
   110  	rand.Seed(time.Now().UTC().UnixNano())
   111  	defer func() {
   112  		rand.Seed(1)
   113  	}()
   114  
   115  	resolver := &DNSCache{
   116  		cache: map[string][]string{
   117  			"play.min.io": {
   118  				"127.0.0.1",
   119  				"127.0.0.2",
   120  				"127.0.0.3",
   121  			},
   122  		},
   123  	}
   124  
   125  	count := make(map[string]int)
   126  	dialF := func(ctx context.Context, network, addr string) (net.Conn, error) {
   127  		count[addr]++
   128  		return nil, nil
   129  	}
   130  
   131  	for i := 0; i < 100; i++ {
   132  		if _, err := DialContextWithDNSCache(resolver, dialF)(context.Background(), "tcp", "play.min.io:443"); err != nil {
   133  			t.Fatalf("err: %s", err)
   134  		}
   135  	}
   136  
   137  	for _, c := range count {
   138  		got := float32(c) / float32(100)
   139  		if got < float32(0.1) {
   140  			t.Fatalf("expected 0.1 rate got %f", got)
   141  		}
   142  	}
   143  }
   144  
   145  // Verify without port Dial fails, Go stdlib net.Dial expects port
   146  func TestDialContextWithDNSCacheScenario1(t *testing.T) {
   147  	resolver := testDNSCache(t)
   148  	if _, err := DialContextWithDNSCache(resolver, nil)(context.Background(), "tcp", "play.min.io"); err == nil {
   149  		t.Fatalf("expect to fail") // expected port
   150  	}
   151  }
   152  
   153  // Verify if the host lookup function failed to return addresses
   154  func TestDialContextWithDNSCacheScenario2(t *testing.T) {
   155  	res := testDNSCache(t)
   156  	originalFunc := res.lookupHostFn
   157  	defer func() {
   158  		res.lookupHostFn = originalFunc
   159  	}()
   160  
   161  	res.lookupHostFn = func(ctx context.Context, host string) ([]string, error) {
   162  		return nil, fmt.Errorf("err")
   163  	}
   164  
   165  	if _, err := DialContextWithDNSCache(res, nil)(context.Background(), "tcp", "min.io:443"); err == nil {
   166  		t.Fatalf("exect to fail")
   167  	}
   168  }
   169  
   170  // Verify we always return the first error from net.Dial failure
   171  func TestDialContextWithDNSCacheScenario3(t *testing.T) {
   172  	resolver := &DNSCache{
   173  		cache: map[string][]string{
   174  			"min.io": {
   175  				"1.1.1.1",
   176  				"2.2.2.2",
   177  				"3.3.3.3",
   178  			},
   179  		},
   180  	}
   181  
   182  	origFunc := randPerm
   183  	randPerm = func(n int) []int {
   184  		return []int{0, 1, 2}
   185  	}
   186  	defer func() {
   187  		randPerm = origFunc
   188  	}()
   189  
   190  	want := errors.New("error1")
   191  	dialF := func(ctx context.Context, network, addr string) (net.Conn, error) {
   192  		if addr == net.JoinHostPort("1.1.1.1", "443") {
   193  			return nil, want // first error should be returned
   194  		}
   195  		if addr == net.JoinHostPort("2.2.2.2", "443") {
   196  			return nil, fmt.Errorf("error2")
   197  		}
   198  		if addr == net.JoinHostPort("3.3.3.3", "443") {
   199  			return nil, fmt.Errorf("error3")
   200  		}
   201  		return nil, nil
   202  	}
   203  
   204  	_, got := DialContextWithDNSCache(resolver, dialF)(context.Background(), "tcp", "min.io:443")
   205  	if got != want {
   206  		t.Fatalf("got error %v, want %v", got, want)
   207  	}
   208  }