github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/endpoint_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"fmt"
    22  	"net"
    23  	"net/url"
    24  	"path/filepath"
    25  	"reflect"
    26  	"strings"
    27  	"testing"
    28  )
    29  
    30  func TestNewEndpoint(t *testing.T) {
    31  	u2, _ := url.Parse("https://example.org/path")
    32  	u4, _ := url.Parse("http://192.168.253.200/path")
    33  	rootSlashFoo, _ := filepath.Abs("/foo")
    34  	testCases := []struct {
    35  		arg              string
    36  		expectedEndpoint Endpoint
    37  		expectedType     EndpointType
    38  		expectedErr      error
    39  	}{
    40  		{"/foo", Endpoint{&url.URL{Path: rootSlashFoo}, true, -1, -1, -1}, PathEndpointType, nil},
    41  		{"https://example.org/path", Endpoint{u2, false, -1, -1, -1}, URLEndpointType, nil},
    42  		{"http://192.168.253.200/path", Endpoint{u4, false, -1, -1, -1}, URLEndpointType, nil},
    43  		{"", Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")},
    44  		{SlashSeparator, Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")},
    45  		{`\`, Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")},
    46  		{"c://foo", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format")},
    47  		{"ftp://foo", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format")},
    48  		{"http://server/path?location", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format")},
    49  		{"http://:/path", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: invalid port number")},
    50  		{"http://:8080/path", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: empty host name")},
    51  		{"http://server:/path", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: invalid port number")},
    52  		{"https://93.184.216.34:808080/path", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: port number must be between 1 to 65535")},
    53  		{"http://server:8080//", Endpoint{}, -1, fmt.Errorf("empty or root path is not supported in URL endpoint")},
    54  		{"http://server:8080/", Endpoint{}, -1, fmt.Errorf("empty or root path is not supported in URL endpoint")},
    55  		{"192.168.1.210:9000", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: missing scheme http or https")},
    56  	}
    57  
    58  	for i, test := range testCases {
    59  		t.Run(fmt.Sprint("case-", i), func(t *testing.T) {
    60  			endpoint, err := NewEndpoint(test.arg)
    61  			if err == nil {
    62  				err = endpoint.UpdateIsLocal()
    63  			}
    64  
    65  			switch {
    66  			case test.expectedErr == nil:
    67  				if err != nil {
    68  					t.Errorf("error: expected = <nil>, got = %v", err)
    69  				}
    70  			case err == nil:
    71  				t.Errorf("error: expected = %v, got = <nil>", test.expectedErr)
    72  			case test.expectedErr.Error() != err.Error():
    73  				t.Errorf("error: expected = %v, got = %v", test.expectedErr, err)
    74  			}
    75  
    76  			if err == nil {
    77  				if (test.expectedEndpoint.URL == nil) != (endpoint.URL == nil) {
    78  					t.Errorf("endpoint url: expected = %#v, got = %#v", test.expectedEndpoint.URL, endpoint.URL)
    79  					return
    80  				} else if test.expectedEndpoint.URL.String() != endpoint.URL.String() {
    81  					t.Errorf("endpoint url: expected = %#v, got = %#v", test.expectedEndpoint.URL.String(), endpoint.URL.String())
    82  					return
    83  				}
    84  				if !reflect.DeepEqual(test.expectedEndpoint, endpoint) {
    85  					t.Errorf("endpoint: expected = %#v, got = %#v", test.expectedEndpoint, endpoint)
    86  				}
    87  			}
    88  
    89  			if err == nil && test.expectedType != endpoint.Type() {
    90  				t.Errorf("type: expected = %+v, got = %+v", test.expectedType, endpoint.Type())
    91  			}
    92  		})
    93  	}
    94  }
    95  
    96  func TestNewEndpoints(t *testing.T) {
    97  	testCases := []struct {
    98  		args        []string
    99  		expectedErr error
   100  	}{
   101  		{[]string{"/d1", "/d2", "/d3", "/d4"}, nil},
   102  		{[]string{"http://localhost/d1", "http://localhost/d2", "http://localhost/d3", "http://localhost/d4"}, nil},
   103  		{[]string{"http://example.org/d1", "http://example.com/d1", "http://example.net/d1", "http://example.edu/d1"}, nil},
   104  		{[]string{"http://localhost/d1", "http://localhost/d2", "http://example.org/d1", "http://example.org/d2"}, nil},
   105  		{[]string{"https://localhost:9000/d1", "https://localhost:9001/d2", "https://localhost:9002/d3", "https://localhost:9003/d4"}, nil},
   106  		// // It is valid WRT endpoint list that same path is expected with different port on same server.
   107  		{[]string{"https://127.0.0.1:9000/d1", "https://127.0.0.1:9001/d1", "https://127.0.0.1:9002/d1", "https://127.0.0.1:9003/d1"}, nil},
   108  		{[]string{"d1", "d2", "d3", "d1"}, fmt.Errorf("duplicate endpoints found")},
   109  		{[]string{"d1", "d2", "d3", "./d1"}, fmt.Errorf("duplicate endpoints found")},
   110  		{[]string{"http://localhost/d1", "http://localhost/d2", "http://localhost/d1", "http://localhost/d4"}, fmt.Errorf("duplicate endpoints found")},
   111  		{[]string{"ftp://server/d1", "http://server/d2", "http://server/d3", "http://server/d4"}, fmt.Errorf("'ftp://server/d1': invalid URL endpoint format")},
   112  		{[]string{"d1", "http://localhost/d2", "d3", "d4"}, fmt.Errorf("mixed style endpoints are not supported")},
   113  		{[]string{"http://example.org/d1", "https://example.com/d1", "http://example.net/d1", "https://example.edut/d1"}, fmt.Errorf("mixed scheme is not supported")},
   114  		{[]string{"192.168.1.210:9000/tmp/dir0", "192.168.1.210:9000/tmp/dir1", "192.168.1.210:9000/tmp/dir2", "192.168.110:9000/tmp/dir3"}, fmt.Errorf("'192.168.1.210:9000/tmp/dir0': invalid URL endpoint format: missing scheme http or https")},
   115  	}
   116  
   117  	for _, testCase := range testCases {
   118  		_, err := NewEndpoints(testCase.args...)
   119  		switch {
   120  		case testCase.expectedErr == nil:
   121  			if err != nil {
   122  				t.Fatalf("error: expected = <nil>, got = %v", err)
   123  			}
   124  		case err == nil:
   125  			t.Fatalf("error: expected = %v, got = <nil>", testCase.expectedErr)
   126  		case testCase.expectedErr.Error() != err.Error():
   127  			t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err)
   128  		}
   129  	}
   130  }
   131  
   132  func TestCreateEndpoints(t *testing.T) {
   133  	tempGlobalMinioPort := globalMinioPort
   134  	defer func() {
   135  		globalMinioPort = tempGlobalMinioPort
   136  	}()
   137  	globalMinioPort = "9000"
   138  
   139  	// Filter ipList by IPs those do not start with '127.'.
   140  	nonLoopBackIPs := localIP4.FuncMatch(func(ip string, matchString string) bool {
   141  		return !net.ParseIP(ip).IsLoopback()
   142  	}, "")
   143  	if len(nonLoopBackIPs) == 0 {
   144  		t.Fatalf("No non-loop back IP address found for this host")
   145  	}
   146  	nonLoopBackIP := nonLoopBackIPs.ToSlice()[0]
   147  
   148  	mustAbs := func(s string) string {
   149  		s, err := filepath.Abs(s)
   150  		if err != nil {
   151  			t.Fatal(err)
   152  		}
   153  		return s
   154  	}
   155  	getExpectedEndpoints := func(args []string, prefix string) ([]*url.URL, []bool) {
   156  		var URLs []*url.URL
   157  		var localFlags []bool
   158  		for _, arg := range args {
   159  			u, _ := url.Parse(arg)
   160  			URLs = append(URLs, u)
   161  			localFlags = append(localFlags, strings.HasPrefix(arg, prefix))
   162  		}
   163  
   164  		return URLs, localFlags
   165  	}
   166  
   167  	case1Endpoint1 := "http://" + nonLoopBackIP + "/d1"
   168  	case1Endpoint2 := "http://" + nonLoopBackIP + "/d2"
   169  	args := []string{
   170  		"http://" + nonLoopBackIP + ":10000/d1",
   171  		"http://" + nonLoopBackIP + ":10000/d2",
   172  		"http://example.org:10000/d3",
   173  		"http://example.com:10000/d4",
   174  	}
   175  	case1URLs, case1LocalFlags := getExpectedEndpoints(args, "http://"+nonLoopBackIP+":10000/")
   176  
   177  	case2Endpoint1 := "http://" + nonLoopBackIP + "/d1"
   178  	case2Endpoint2 := "http://" + nonLoopBackIP + ":9000/d2"
   179  	args = []string{
   180  		"http://" + nonLoopBackIP + ":10000/d1",
   181  		"http://" + nonLoopBackIP + ":9000/d2",
   182  		"http://example.org:10000/d3",
   183  		"http://example.com:10000/d4",
   184  	}
   185  	case2URLs, case2LocalFlags := getExpectedEndpoints(args, "http://"+nonLoopBackIP+":10000/")
   186  
   187  	case3Endpoint1 := "http://" + nonLoopBackIP + "/d1"
   188  	args = []string{
   189  		"http://" + nonLoopBackIP + ":80/d1",
   190  		"http://example.org:9000/d2",
   191  		"http://example.com:80/d3",
   192  		"http://example.net:80/d4",
   193  	}
   194  	case3URLs, case3LocalFlags := getExpectedEndpoints(args, "http://"+nonLoopBackIP+":80/")
   195  
   196  	case4Endpoint1 := "http://" + nonLoopBackIP + "/d1"
   197  	args = []string{
   198  		"http://" + nonLoopBackIP + ":9000/d1",
   199  		"http://example.org:9000/d2",
   200  		"http://example.com:9000/d3",
   201  		"http://example.net:9000/d4",
   202  	}
   203  	case4URLs, case4LocalFlags := getExpectedEndpoints(args, "http://"+nonLoopBackIP+":9000/")
   204  
   205  	case5Endpoint1 := "http://" + nonLoopBackIP + ":9000/d1"
   206  	case5Endpoint2 := "http://" + nonLoopBackIP + ":9001/d2"
   207  	case5Endpoint3 := "http://" + nonLoopBackIP + ":9002/d3"
   208  	case5Endpoint4 := "http://" + nonLoopBackIP + ":9003/d4"
   209  	args = []string{
   210  		case5Endpoint1,
   211  		case5Endpoint2,
   212  		case5Endpoint3,
   213  		case5Endpoint4,
   214  	}
   215  	case5URLs, case5LocalFlags := getExpectedEndpoints(args, "http://"+nonLoopBackIP+":9000/")
   216  
   217  	case6Endpoint := "http://" + nonLoopBackIP + ":9003/d4"
   218  	args = []string{
   219  		"http://localhost:9000/d1",
   220  		"http://localhost:9001/d2",
   221  		"http://127.0.0.1:9002/d3",
   222  		case6Endpoint,
   223  	}
   224  	case6URLs, case6LocalFlags := getExpectedEndpoints(args, "http://"+nonLoopBackIP+":9003/")
   225  
   226  	testCases := []struct {
   227  		serverAddr         string
   228  		args               []string
   229  		expectedServerAddr string
   230  		expectedEndpoints  Endpoints
   231  		expectedSetupType  SetupType
   232  		expectedErr        error
   233  	}{
   234  		{"localhost", []string{}, "", Endpoints{}, -1, fmt.Errorf("address localhost: missing port in address")},
   235  
   236  		// Erasure Single Drive
   237  		{"localhost:9000", []string{"http://localhost/d1"}, "", Endpoints{}, -1, fmt.Errorf("use path style endpoint for SD setup")},
   238  		{":443", []string{"/d1"}, ":443", Endpoints{Endpoint{URL: &url.URL{Path: mustAbs("/d1")}, IsLocal: true}}, ErasureSDSetupType, nil},
   239  		{"localhost:10000", []string{"/d1"}, "localhost:10000", Endpoints{Endpoint{URL: &url.URL{Path: mustAbs("/d1")}, IsLocal: true}}, ErasureSDSetupType, nil},
   240  		{"localhost:9000", []string{"https://127.0.0.1:9000/d1", "https://localhost:9001/d1", "https://example.com/d1", "https://example.com/d2"}, "", Endpoints{}, -1, fmt.Errorf("path '/d1' can not be served by different port on same address")},
   241  
   242  		// Erasure Setup with PathEndpointType
   243  		{
   244  			":1234",
   245  			[]string{"/d1", "/d2", "/d3", "/d4"},
   246  			":1234",
   247  			Endpoints{
   248  				Endpoint{URL: &url.URL{Path: mustAbs("/d1")}, IsLocal: true},
   249  				Endpoint{URL: &url.URL{Path: mustAbs("/d2")}, IsLocal: true},
   250  				Endpoint{URL: &url.URL{Path: mustAbs("/d3")}, IsLocal: true},
   251  				Endpoint{URL: &url.URL{Path: mustAbs("/d4")}, IsLocal: true},
   252  			},
   253  			ErasureSetupType, nil,
   254  		},
   255  		// DistErasure Setup with URLEndpointType
   256  		{":9000", []string{"http://localhost/d1", "http://localhost/d2", "http://localhost/d3", "http://localhost/d4"}, ":9000", Endpoints{
   257  			Endpoint{URL: &url.URL{Scheme: "http", Host: "localhost:9000", Path: "/d1"}, IsLocal: true},
   258  			Endpoint{URL: &url.URL{Scheme: "http", Host: "localhost:9000", Path: "/d2"}, IsLocal: true},
   259  			Endpoint{URL: &url.URL{Scheme: "http", Host: "localhost:9000", Path: "/d3"}, IsLocal: true},
   260  			Endpoint{URL: &url.URL{Scheme: "http", Host: "localhost:9000", Path: "/d4"}, IsLocal: true},
   261  		}, ErasureSetupType, nil},
   262  		// DistErasure Setup with URLEndpointType having mixed naming to local host.
   263  		{"127.0.0.1:10000", []string{"http://localhost/d1", "http://localhost/d2", "http://127.0.0.1/d3", "http://127.0.0.1/d4"}, "", Endpoints{}, -1, fmt.Errorf("all local endpoints should not have different hostnames/ips")},
   264  
   265  		{":9001", []string{"http://10.0.0.1:9000/export", "http://10.0.0.2:9000/export", "http://" + nonLoopBackIP + ":9001/export", "http://10.0.0.2:9001/export"}, "", Endpoints{}, -1, fmt.Errorf("path '/export' can not be served by different port on same address")},
   266  
   267  		{":9000", []string{"http://127.0.0.1:9000/export", "http://" + nonLoopBackIP + ":9000/export", "http://10.0.0.1:9000/export", "http://10.0.0.2:9000/export"}, "", Endpoints{}, -1, fmt.Errorf("path '/export' cannot be served by different address on same server")},
   268  
   269  		// DistErasure type
   270  		{"127.0.0.1:10000", []string{case1Endpoint1, case1Endpoint2, "http://example.org/d3", "http://example.com/d4"}, "127.0.0.1:10000", Endpoints{
   271  			Endpoint{URL: case1URLs[0], IsLocal: case1LocalFlags[0]},
   272  			Endpoint{URL: case1URLs[1], IsLocal: case1LocalFlags[1]},
   273  			Endpoint{URL: case1URLs[2], IsLocal: case1LocalFlags[2]},
   274  			Endpoint{URL: case1URLs[3], IsLocal: case1LocalFlags[3]},
   275  		}, DistErasureSetupType, nil},
   276  
   277  		{"127.0.0.1:10000", []string{case2Endpoint1, case2Endpoint2, "http://example.org/d3", "http://example.com/d4"}, "127.0.0.1:10000", Endpoints{
   278  			Endpoint{URL: case2URLs[0], IsLocal: case2LocalFlags[0]},
   279  			Endpoint{URL: case2URLs[1], IsLocal: case2LocalFlags[1]},
   280  			Endpoint{URL: case2URLs[2], IsLocal: case2LocalFlags[2]},
   281  			Endpoint{URL: case2URLs[3], IsLocal: case2LocalFlags[3]},
   282  		}, DistErasureSetupType, nil},
   283  
   284  		{":80", []string{case3Endpoint1, "http://example.org:9000/d2", "http://example.com/d3", "http://example.net/d4"}, ":80", Endpoints{
   285  			Endpoint{URL: case3URLs[0], IsLocal: case3LocalFlags[0]},
   286  			Endpoint{URL: case3URLs[1], IsLocal: case3LocalFlags[1]},
   287  			Endpoint{URL: case3URLs[2], IsLocal: case3LocalFlags[2]},
   288  			Endpoint{URL: case3URLs[3], IsLocal: case3LocalFlags[3]},
   289  		}, DistErasureSetupType, nil},
   290  
   291  		{":9000", []string{case4Endpoint1, "http://example.org/d2", "http://example.com/d3", "http://example.net/d4"}, ":9000", Endpoints{
   292  			Endpoint{URL: case4URLs[0], IsLocal: case4LocalFlags[0]},
   293  			Endpoint{URL: case4URLs[1], IsLocal: case4LocalFlags[1]},
   294  			Endpoint{URL: case4URLs[2], IsLocal: case4LocalFlags[2]},
   295  			Endpoint{URL: case4URLs[3], IsLocal: case4LocalFlags[3]},
   296  		}, DistErasureSetupType, nil},
   297  
   298  		{":9000", []string{case5Endpoint1, case5Endpoint2, case5Endpoint3, case5Endpoint4}, ":9000", Endpoints{
   299  			Endpoint{URL: case5URLs[0], IsLocal: case5LocalFlags[0]},
   300  			Endpoint{URL: case5URLs[1], IsLocal: case5LocalFlags[1]},
   301  			Endpoint{URL: case5URLs[2], IsLocal: case5LocalFlags[2]},
   302  			Endpoint{URL: case5URLs[3], IsLocal: case5LocalFlags[3]},
   303  		}, DistErasureSetupType, nil},
   304  
   305  		// DistErasure Setup using only local host.
   306  		{":9003", []string{"http://localhost:9000/d1", "http://localhost:9001/d2", "http://127.0.0.1:9002/d3", case6Endpoint}, ":9003", Endpoints{
   307  			Endpoint{URL: case6URLs[0], IsLocal: case6LocalFlags[0]},
   308  			Endpoint{URL: case6URLs[1], IsLocal: case6LocalFlags[1]},
   309  			Endpoint{URL: case6URLs[2], IsLocal: case6LocalFlags[2]},
   310  			Endpoint{URL: case6URLs[3], IsLocal: case6LocalFlags[3]},
   311  		}, DistErasureSetupType, nil},
   312  	}
   313  
   314  	for i, testCase := range testCases {
   315  		i := i
   316  		testCase := testCase
   317  		t.Run("", func(t *testing.T) {
   318  			var srvCtxt serverCtxt
   319  			err := mergeDisksLayoutFromArgs(testCase.args, &srvCtxt)
   320  			if err != nil && testCase.expectedErr == nil {
   321  				t.Errorf("Test %d: unexpected error: %v", i+1, err)
   322  			}
   323  			pools, setupType, err := CreatePoolEndpoints(testCase.serverAddr, srvCtxt.Layout.pools...)
   324  			if err == nil && testCase.expectedErr != nil {
   325  				t.Errorf("Test %d: expected = %v, got = <nil>", i+1, testCase.expectedErr)
   326  			}
   327  			if err == nil {
   328  				if setupType != testCase.expectedSetupType {
   329  					t.Errorf("Test %d: setupType: expected = %v, got = %v", i+1, testCase.expectedSetupType, setupType)
   330  				}
   331  				endpoints := pools[0]
   332  				if len(endpoints) != len(testCase.expectedEndpoints) {
   333  					t.Errorf("Test %d: endpoints: expected = %d, got = %d", i+1, len(testCase.expectedEndpoints),
   334  						len(endpoints))
   335  				} else {
   336  					for i, endpoint := range endpoints {
   337  						if testCase.expectedEndpoints[i].String() != endpoint.String() {
   338  							t.Errorf("Test %d: endpoints: expected = %s, got = %s",
   339  								i+1,
   340  								testCase.expectedEndpoints[i],
   341  								endpoint)
   342  						}
   343  					}
   344  				}
   345  			}
   346  			if err != nil && testCase.expectedErr == nil {
   347  				t.Errorf("Test %d: error: expected = <nil>, got = %v, testCase: %v", i+1, err, testCase)
   348  			}
   349  		})
   350  	}
   351  }
   352  
   353  // Tests get local peer functionality, local peer is supposed to only return one entry per minio service.
   354  // So it means that if you have say localhost:9000 and localhost:9001 as endpointArgs then localhost:9001
   355  // is considered a remote service from localhost:9000 perspective.
   356  func TestGetLocalPeer(t *testing.T) {
   357  	tempGlobalMinioPort := globalMinioPort
   358  	defer func() {
   359  		globalMinioPort = tempGlobalMinioPort
   360  	}()
   361  	globalMinioPort = "9000"
   362  
   363  	testCases := []struct {
   364  		endpointArgs   []string
   365  		expectedResult string
   366  	}{
   367  		{[]string{"/d1", "/d2", "d3", "d4"}, "127.0.0.1:9000"},
   368  		{
   369  			[]string{"http://localhost:9000/d1", "http://localhost:9000/d2", "http://example.org:9000/d3", "http://example.com:9000/d4"},
   370  			"localhost:9000",
   371  		},
   372  		{
   373  			[]string{"http://localhost:9000/d1", "http://example.org:9000/d2", "http://example.com:9000/d3", "http://example.net:9000/d4"},
   374  			"localhost:9000",
   375  		},
   376  		{
   377  			[]string{"http://localhost:9000/d1", "http://localhost:9001/d2", "http://localhost:9002/d3", "http://localhost:9003/d4"},
   378  			"localhost:9000",
   379  		},
   380  	}
   381  
   382  	for i, testCase := range testCases {
   383  		zendpoints := mustGetPoolEndpoints(0, testCase.endpointArgs...)
   384  		if !zendpoints[0].Endpoints[0].IsLocal {
   385  			if err := zendpoints[0].Endpoints.UpdateIsLocal(); err != nil {
   386  				t.Fatalf("error: expected = <nil>, got = %v", err)
   387  			}
   388  		}
   389  		localPeer := GetLocalPeer(zendpoints, "", "9000")
   390  		if localPeer != testCase.expectedResult {
   391  			t.Fatalf("Test %d: expected: %v, got: %v", i+1, testCase.expectedResult, localPeer)
   392  		}
   393  	}
   394  }
   395  
   396  func TestGetRemotePeers(t *testing.T) {
   397  	tempGlobalMinioPort := globalMinioPort
   398  	defer func() {
   399  		globalMinioPort = tempGlobalMinioPort
   400  	}()
   401  	globalMinioPort = "9000"
   402  
   403  	testCases := []struct {
   404  		endpointArgs   []string
   405  		expectedResult []string
   406  		expectedLocal  string
   407  	}{
   408  		{[]string{"/d1", "/d2", "d3", "d4"}, []string{}, ""},
   409  		{[]string{"http://localhost:9000/d1", "http://localhost:9000/d2", "http://example.org:9000/d3", "http://example.com:9000/d4"}, []string{"example.com:9000", "example.org:9000", "localhost:9000"}, "localhost:9000"},
   410  		{[]string{"http://localhost:9000/d1", "http://localhost:10000/d2", "http://example.org:9000/d3", "http://example.com:9000/d4"}, []string{"example.com:9000", "example.org:9000", "localhost:10000", "localhost:9000"}, "localhost:9000"},
   411  		{[]string{"http://localhost:9000/d1", "http://example.org:9000/d2", "http://example.com:9000/d3", "http://example.net:9000/d4"}, []string{"example.com:9000", "example.net:9000", "example.org:9000", "localhost:9000"}, "localhost:9000"},
   412  		{[]string{"http://localhost:9000/d1", "http://localhost:9001/d2", "http://localhost:9002/d3", "http://localhost:9003/d4"}, []string{"localhost:9000", "localhost:9001", "localhost:9002", "localhost:9003"}, "localhost:9000"},
   413  	}
   414  
   415  	for _, testCase := range testCases {
   416  		zendpoints := mustGetPoolEndpoints(0, testCase.endpointArgs...)
   417  		if !zendpoints[0].Endpoints[0].IsLocal {
   418  			if err := zendpoints[0].Endpoints.UpdateIsLocal(); err != nil {
   419  				t.Errorf("error: expected = <nil>, got = %v", err)
   420  			}
   421  		}
   422  		remotePeers, local := zendpoints.peers()
   423  		if !reflect.DeepEqual(remotePeers, testCase.expectedResult) {
   424  			t.Errorf("expected: %v, got: %v", testCase.expectedResult, remotePeers)
   425  		}
   426  		if local != testCase.expectedLocal {
   427  			t.Errorf("expected: %v, got: %v", testCase.expectedLocal, local)
   428  		}
   429  	}
   430  }