mirror of
https://github.com/minio/minio.git
synced 2024-12-25 22:55:54 -05:00
5d65428b29
Fixes an issue reported by @klauspost and @vadmeste This PR also allows users to expand their clusters from single node XL deployment to distributed mode.
366 lines
11 KiB
Go
366 lines
11 KiB
Go
/*
|
|
* MinIO Cloud Storage, (C) 2017 MinIO, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"reflect"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/minio/minio-go/v6/pkg/set"
|
|
)
|
|
|
|
func TestMustSplitHostPort(t *testing.T) {
|
|
testCases := []struct {
|
|
hostPort string
|
|
expectedHost string
|
|
expectedPort string
|
|
}{
|
|
{":54321", "", "54321"},
|
|
{"server:54321", "server", "54321"},
|
|
{":0", "", "0"},
|
|
{"server:https", "server", "443"},
|
|
{"server:http", "server", "80"},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
host, port := mustSplitHostPort(testCase.hostPort)
|
|
if testCase.expectedHost != host {
|
|
t.Fatalf("host: expected = %v, got = %v", testCase.expectedHost, host)
|
|
}
|
|
|
|
if testCase.expectedPort != port {
|
|
t.Fatalf("port: expected = %v, got = %v", testCase.expectedPort, port)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSortIPs(t *testing.T) {
|
|
testCases := []struct {
|
|
ipList []string
|
|
sortedIPList []string
|
|
}{
|
|
// Default case of two ips one with higher octet moves
|
|
// to the beginning of the list.
|
|
{
|
|
ipList: []string{"127.0.0.1", "10.0.0.13"},
|
|
sortedIPList: []string{"10.0.0.13", "127.0.0.1"},
|
|
},
|
|
// With multiple types of octet, chooses a higher octet.
|
|
{
|
|
ipList: []string{"127.0.0.1", "172.0.21.1", "192.168.1.106"},
|
|
sortedIPList: []string{"192.168.1.106", "172.0.21.1", "127.0.0.1"},
|
|
},
|
|
// With different ip along with localhost.
|
|
{
|
|
ipList: []string{"127.0.0.1", "192.168.1.106"},
|
|
sortedIPList: []string{"192.168.1.106", "127.0.0.1"},
|
|
},
|
|
// With a list of only one element nothing to sort.
|
|
{
|
|
ipList: []string{"hostname"},
|
|
sortedIPList: []string{"hostname"},
|
|
},
|
|
// With a list of only one element nothing to sort.
|
|
{
|
|
ipList: []string{"127.0.0.1"},
|
|
sortedIPList: []string{"127.0.0.1"},
|
|
},
|
|
// Non parsable ip is assumed to be hostame and gets preserved
|
|
// as the left most elements, regardless of IP based sorting.
|
|
{
|
|
ipList: []string{"hostname", "127.0.0.1", "192.168.1.106"},
|
|
sortedIPList: []string{"hostname", "192.168.1.106", "127.0.0.1"},
|
|
},
|
|
// Non parsable ip is assumed to be hostname, with a mixed input of ip and hostname.
|
|
// gets preserved and moved into left most elements, regardless of
|
|
// IP based sorting.
|
|
{
|
|
ipList: []string{"hostname1", "10.0.0.13", "hostname2", "127.0.0.1", "192.168.1.106"},
|
|
sortedIPList: []string{"hostname1", "hostname2", "192.168.1.106", "10.0.0.13", "127.0.0.1"},
|
|
},
|
|
// With same higher octets, preferentially move the localhost.
|
|
{
|
|
ipList: []string{"127.0.0.1", "10.0.0.1", "192.168.0.1"},
|
|
sortedIPList: []string{"10.0.0.1", "192.168.0.1", "127.0.0.1"},
|
|
},
|
|
}
|
|
for i, testCase := range testCases {
|
|
gotIPList := sortIPs(testCase.ipList)
|
|
if !reflect.DeepEqual(testCase.sortedIPList, gotIPList) {
|
|
t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.sortedIPList, gotIPList)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMustGetLocalIP4(t *testing.T) {
|
|
testCases := []struct {
|
|
expectedIPList set.StringSet
|
|
}{
|
|
{set.CreateStringSet("127.0.0.1")},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
ipList := mustGetLocalIP4()
|
|
if testCase.expectedIPList != nil && testCase.expectedIPList.Intersection(ipList).IsEmpty() {
|
|
t.Fatalf("host: expected = %v, got = %v", testCase.expectedIPList, ipList)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetHostIP(t *testing.T) {
|
|
testCases := []struct {
|
|
host string
|
|
expectedIPList set.StringSet
|
|
expectedErr error
|
|
}{
|
|
{"localhost", set.CreateStringSet("127.0.0.1"), nil},
|
|
{"example.org", set.CreateStringSet("93.184.216.34"), nil},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
ipList, err := getHostIP(testCase.host)
|
|
if testCase.expectedErr == nil {
|
|
if err != nil {
|
|
t.Fatalf("error: expected = <nil>, got = %v", err)
|
|
}
|
|
} else if err == nil {
|
|
t.Fatalf("error: expected = %v, got = <nil>", testCase.expectedErr)
|
|
} else if testCase.expectedErr.Error() != err.Error() {
|
|
t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err)
|
|
}
|
|
|
|
if testCase.expectedIPList != nil && testCase.expectedIPList.Intersection(ipList).IsEmpty() {
|
|
t.Fatalf("host: expected = %v, got = %v", testCase.expectedIPList, ipList)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests finalize api endpoints.
|
|
func TestGetAPIEndpoints(t *testing.T) {
|
|
host, port := globalMinioHost, globalMinioAddr
|
|
defer func() {
|
|
globalMinioHost, globalMinioAddr = host, port
|
|
}()
|
|
testCases := []struct {
|
|
host, port string
|
|
expectedResult string
|
|
}{
|
|
{"", "80", "http://127.0.0.1:80"},
|
|
{"127.0.0.1", "80", "http://127.0.0.1:80"},
|
|
{"localhost", "80", "http://localhost:80"},
|
|
}
|
|
|
|
for i, testCase := range testCases {
|
|
globalMinioHost, globalMinioPort = testCase.host, testCase.port
|
|
apiEndpoints := getAPIEndpoints()
|
|
apiEndpointSet := set.CreateStringSet(apiEndpoints...)
|
|
if !apiEndpointSet.Contains(testCase.expectedResult) {
|
|
t.Fatalf("test %d: expected: Found, got: Not Found", i+1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ask the kernel for a free open port.
|
|
func getFreePort() string {
|
|
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
l, err := net.ListenTCP("tcp", addr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer l.Close()
|
|
return fmt.Sprintf("%d", l.Addr().(*net.TCPAddr).Port)
|
|
}
|
|
|
|
// Tests for port availability logic written for server startup sequence.
|
|
func TestCheckPortAvailability(t *testing.T) {
|
|
// Make a port is not available.
|
|
port := getFreePort()
|
|
listener, err := net.Listen("tcp", net.JoinHostPort("", port))
|
|
if err != nil {
|
|
t.Fatalf("Unable to listen on port %v", port)
|
|
}
|
|
defer listener.Close()
|
|
|
|
testCases := []struct {
|
|
host string
|
|
port string
|
|
expectedErr error
|
|
}{
|
|
{"", port, fmt.Errorf("listen tcp :%v: bind: address already in use", port)},
|
|
{"127.0.0.1", port, fmt.Errorf("listen tcp 127.0.0.1:%v: bind: address already in use", port)},
|
|
{"", getFreePort(), nil},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
// On MS Windows and Mac, skip checking error case due to https://github.com/golang/go/issues/7598
|
|
if (runtime.GOOS == globalWindowsOSName || runtime.GOOS == globalMacOSName) && testCase.expectedErr != nil {
|
|
continue
|
|
}
|
|
|
|
err := checkPortAvailability(testCase.host, testCase.port)
|
|
if testCase.expectedErr == nil {
|
|
if err != nil {
|
|
t.Fatalf("error: expected = <nil>, got = %v", err)
|
|
}
|
|
} else if err == nil {
|
|
t.Fatalf("error: expected = %v, got = <nil>", testCase.expectedErr)
|
|
} else if testCase.expectedErr.Error() != err.Error() {
|
|
t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCheckLocalServerAddr(t *testing.T) {
|
|
testCases := []struct {
|
|
serverAddr string
|
|
expectedErr error
|
|
}{
|
|
{":54321", nil},
|
|
{"localhost:54321", nil},
|
|
{"0.0.0.0:9000", nil},
|
|
{":0", nil},
|
|
{"localhost", nil},
|
|
{"", fmt.Errorf("invalid argument")},
|
|
{"example.org:54321", fmt.Errorf("host in server address should be this server")},
|
|
{":-10", fmt.Errorf("port must be between 0 to 65535")},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
testCase := testCase
|
|
t.Run("", func(t *testing.T) {
|
|
err := CheckLocalServerAddr(testCase.serverAddr)
|
|
if testCase.expectedErr == nil {
|
|
if err != nil {
|
|
t.Errorf("error: expected = <nil>, got = %v", err)
|
|
}
|
|
} else if err == nil {
|
|
t.Errorf("error: expected = %v, got = <nil>", testCase.expectedErr)
|
|
} else if testCase.expectedErr.Error() != err.Error() {
|
|
t.Errorf("error: expected = %v, got = %v", testCase.expectedErr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractHostPort(t *testing.T) {
|
|
testCases := []struct {
|
|
addr string
|
|
host string
|
|
port string
|
|
expectedErr error
|
|
}{
|
|
{"", "", "", errors.New("unable to process empty address")},
|
|
{"localhost:9000", "localhost", "9000", nil},
|
|
{"http://:9000/", "", "9000", nil},
|
|
{"http://8.8.8.8:9000/", "8.8.8.8", "9000", nil},
|
|
{"https://facebook.com:9000/", "facebook.com", "9000", nil},
|
|
}
|
|
|
|
for i, testCase := range testCases {
|
|
host, port, err := extractHostPort(testCase.addr)
|
|
if testCase.expectedErr == nil {
|
|
if err != nil {
|
|
t.Fatalf("Test %d: should succeed but failed with err: %v", i+1, err)
|
|
}
|
|
if host != testCase.host {
|
|
t.Fatalf("Test %d: expected: %v, found: %v", i+1, testCase.host, host)
|
|
}
|
|
if port != testCase.port {
|
|
t.Fatalf("Test %d: expected: %v, found: %v", i+1, testCase.port, port)
|
|
}
|
|
|
|
}
|
|
if testCase.expectedErr != nil {
|
|
if err == nil {
|
|
t.Fatalf("Test %d:, should fail but succeeded.", i+1)
|
|
}
|
|
if testCase.expectedErr.Error() != err.Error() {
|
|
t.Fatalf("Test %d: failed with different error, expected: '%v', found:'%v'.", i+1, testCase.expectedErr, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSameLocalAddrs(t *testing.T) {
|
|
testCases := []struct {
|
|
addr1 string
|
|
addr2 string
|
|
sameAddr bool
|
|
expectedErr error
|
|
}{
|
|
{"", "", false, errors.New("unable to process empty address")},
|
|
{":9000", ":9000", true, nil},
|
|
{"localhost:9000", ":9000", true, nil},
|
|
{"localhost:9000", "http://localhost:9000", true, nil},
|
|
{"http://localhost:9000", ":9000", true, nil},
|
|
{"http://localhost:9000", "http://localhost:9000", true, nil},
|
|
{"http://8.8.8.8:9000", "http://localhost:9000", false, nil},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
testCase := testCase
|
|
t.Run("", func(t *testing.T) {
|
|
sameAddr, err := sameLocalAddrs(testCase.addr1, testCase.addr2)
|
|
if testCase.expectedErr != nil && err == nil {
|
|
t.Errorf("should fail but succeeded")
|
|
}
|
|
if testCase.expectedErr == nil && err != nil {
|
|
t.Errorf("should succeed but failed with %v", err)
|
|
}
|
|
if err == nil {
|
|
if sameAddr != testCase.sameAddr {
|
|
t.Errorf("expected: %v, found: %v", testCase.sameAddr, sameAddr)
|
|
}
|
|
} else {
|
|
if err.Error() != testCase.expectedErr.Error() {
|
|
t.Errorf("failed with different error, expected: '%v', found:'%v'.",
|
|
testCase.expectedErr, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
func TestIsHostIP(t *testing.T) {
|
|
testCases := []struct {
|
|
args string
|
|
expectedResult bool
|
|
}{
|
|
{"localhost", false},
|
|
{"localhost:9000", false},
|
|
{"example.com", false},
|
|
{"http://192.168.1.0", false},
|
|
{"http://192.168.1.0:9000", false},
|
|
{"192.168.1.0", true},
|
|
{"[2001:3984:3989::20%eth0]:9000", true},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
ret := isHostIP(testCase.args)
|
|
if testCase.expectedResult != ret {
|
|
t.Fatalf("expected: %v , got: %v", testCase.expectedResult, ret)
|
|
}
|
|
}
|
|
}
|