mirror of
				https://github.com/minio/minio.git
				synced 2025-10-29 15:55:00 -04:00 
			
		
		
		
	Add extensive endpoints validation (#4019)
This commit is contained in:
		
							parent
							
								
									1b1b9e4801
								
							
						
					
					
						commit
						de204a0a52
					
				| @ -158,12 +158,7 @@ func prepareAdminXLTestBed() (*adminXLTestBed, error) { | ||||
| 	// Initialize boot time | ||||
| 	globalBootTime = UTCNow() | ||||
| 
 | ||||
| 	// Set globalEndpoints for a single node XL setup. | ||||
| 	for _, xlDir := range xlDirs { | ||||
| 		globalEndpoints = append(globalEndpoints, &url.URL{ | ||||
| 			Path: xlDir, | ||||
| 		}) | ||||
| 	} | ||||
| 	globalEndpoints = mustGetNewEndpointList(xlDirs...) | ||||
| 
 | ||||
| 	// Set globalIsXL to indicate that the setup uses an erasure code backend. | ||||
| 	globalIsXL = true | ||||
| @ -301,14 +296,8 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) { | ||||
| 	// Initialize admin peers to make admin RPC calls. Note: In a | ||||
| 	// single node setup, this degenerates to a simple function | ||||
| 	// call under the hood. | ||||
| 	eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse storage end point - %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set globalMinioAddr to be able to distinguish local endpoints from remote. | ||||
| 	globalMinioAddr = eps[0].Host | ||||
| 	initGlobalAdminPeers(eps) | ||||
| 	globalMinioAddr = "127.0.0.1:9000" | ||||
| 	initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1")) | ||||
| 
 | ||||
| 	// Setting up a go routine to simulate ServerMux's | ||||
| 	// handleServiceSignals for stop and restart commands. | ||||
| @ -367,14 +356,8 @@ func TestServiceSetCreds(t *testing.T) { | ||||
| 	// Initialize admin peers to make admin RPC calls. Note: In a | ||||
| 	// single node setup, this degenerates to a simple function | ||||
| 	// call under the hood. | ||||
| 	eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse storage end point - %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set globalMinioAddr to be able to distinguish local endpoints from remote. | ||||
| 	globalMinioAddr = eps[0].Host | ||||
| 	initGlobalAdminPeers(eps) | ||||
| 	globalMinioAddr = "127.0.0.1:9000" | ||||
| 	initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1")) | ||||
| 
 | ||||
| 	credentials := serverConfig.GetCredential() | ||||
| 	var body []byte | ||||
| @ -455,14 +438,8 @@ func TestListLocksHandler(t *testing.T) { | ||||
| 	defer adminTestBed.TearDown() | ||||
| 
 | ||||
| 	// Initialize admin peers to make admin RPC calls. | ||||
| 	eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse storage end point - %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set globalMinioAddr to be able to distinguish local endpoints from remote. | ||||
| 	globalMinioAddr = eps[0].Host | ||||
| 	initGlobalAdminPeers(eps) | ||||
| 	globalMinioAddr = "127.0.0.1:9000" | ||||
| 	initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1")) | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		bucket         string | ||||
| @ -530,11 +507,7 @@ func TestClearLocksHandler(t *testing.T) { | ||||
| 	defer adminTestBed.TearDown() | ||||
| 
 | ||||
| 	// Initialize admin peers to make admin RPC calls. | ||||
| 	eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse storage end point - %v", err) | ||||
| 	} | ||||
| 	initGlobalAdminPeers(eps) | ||||
| 	initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1")) | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		bucket         string | ||||
| @ -1238,14 +1211,8 @@ func TestGetConfigHandler(t *testing.T) { | ||||
| 	defer adminTestBed.TearDown() | ||||
| 
 | ||||
| 	// Initialize admin peers to make admin RPC calls. | ||||
| 	eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse storage end point - %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set globalMinioAddr to be able to distinguish local endpoints from remote. | ||||
| 	globalMinioAddr = eps[0].Host | ||||
| 	initGlobalAdminPeers(eps) | ||||
| 	globalMinioAddr = "127.0.0.1:9000" | ||||
| 	initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1")) | ||||
| 
 | ||||
| 	// Prepare query params for get-config mgmt REST API. | ||||
| 	queryVal := url.Values{} | ||||
| @ -1273,14 +1240,8 @@ func TestSetConfigHandler(t *testing.T) { | ||||
| 	defer adminTestBed.TearDown() | ||||
| 
 | ||||
| 	// Initialize admin peers to make admin RPC calls. | ||||
| 	eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse storage end point - %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set globalMinioAddr to be able to distinguish local endpoints from remote. | ||||
| 	globalMinioAddr = eps[0].Host | ||||
| 	initGlobalAdminPeers(eps) | ||||
| 	globalMinioAddr = "127.0.0.1:9000" | ||||
| 	initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1")) | ||||
| 
 | ||||
| 	// SetConfigHandler restarts minio setup - need to start a | ||||
| 	// signal receiver to receive on globalServiceSignalCh. | ||||
| @ -1321,14 +1282,8 @@ func TestAdminServerInfo(t *testing.T) { | ||||
| 	defer adminTestBed.TearDown() | ||||
| 
 | ||||
| 	// Initialize admin peers to make admin RPC calls. | ||||
| 	eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse storage end point - %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set globalMinioAddr to be able to distinguish local endpoints from remote. | ||||
| 	globalMinioAddr = eps[0].Host | ||||
| 	initGlobalAdminPeers(eps) | ||||
| 	globalMinioAddr = "127.0.0.1:9000" | ||||
| 	initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1")) | ||||
| 
 | ||||
| 	// Prepare query params for set-config mgmt REST API. | ||||
| 	queryVal := url.Values{} | ||||
|  | ||||
| @ -20,7 +20,7 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| @ -28,6 +28,8 @@ import ( | ||||
| 	"sort" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/set" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| @ -211,52 +213,43 @@ type adminPeer struct { | ||||
| type adminPeers []adminPeer | ||||
| 
 | ||||
| // makeAdminPeers - helper function to construct a collection of adminPeer. | ||||
| func makeAdminPeers(eps []*url.URL) adminPeers { | ||||
| 	var servicePeers []adminPeer | ||||
| 
 | ||||
| 	// map to store peers that are already added to ret | ||||
| 	seenAddr := make(map[string]bool) | ||||
| 
 | ||||
| 	// add local (self) as peer in the array | ||||
| 	servicePeers = append(servicePeers, adminPeer{ | ||||
| 		globalMinioAddr, | ||||
| func makeAdminPeers(endpoints EndpointList) (adminPeerList adminPeers) { | ||||
| 	thisPeer := globalMinioAddr | ||||
| 	if globalMinioHost == "" { | ||||
| 		thisPeer = net.JoinHostPort("localhost", globalMinioPort) | ||||
| 	} | ||||
| 	adminPeerList = append(adminPeerList, adminPeer{ | ||||
| 		thisPeer, | ||||
| 		localAdminClient{}, | ||||
| 	}) | ||||
| 	seenAddr[globalMinioAddr] = true | ||||
| 
 | ||||
| 	serverCred := serverConfig.GetCredential() | ||||
| 	// iterate over endpoints to find new remote peers and add | ||||
| 	// them to ret. | ||||
| 	for _, ep := range eps { | ||||
| 		if ep.Host == "" { | ||||
| 	hostSet := set.CreateStringSet(globalMinioAddr) | ||||
| 	cred := serverConfig.GetCredential() | ||||
| 	serviceEndpoint := path.Join(minioReservedBucketPath, adminPath) | ||||
| 	for _, host := range GetRemotePeers(endpoints) { | ||||
| 		if hostSet.Contains(host) { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// Check if the remote host has been added already | ||||
| 		if !seenAddr[ep.Host] { | ||||
| 			cfg := authConfig{ | ||||
| 				accessKey:       serverCred.AccessKey, | ||||
| 				secretKey:       serverCred.SecretKey, | ||||
| 				serverAddr:      ep.Host, | ||||
| 		hostSet.Add(host) | ||||
| 		adminPeerList = append(adminPeerList, adminPeer{ | ||||
| 			addr: host, | ||||
| 			cmdRunner: &remoteAdminClient{newAuthRPCClient(authConfig{ | ||||
| 				accessKey:       cred.AccessKey, | ||||
| 				secretKey:       cred.SecretKey, | ||||
| 				serverAddr:      host, | ||||
| 				serviceEndpoint: serviceEndpoint, | ||||
| 				secureConn:      globalIsSSL, | ||||
| 				serviceEndpoint: path.Join(minioReservedBucketPath, adminPath), | ||||
| 				serviceName:     "Admin", | ||||
| 			} | ||||
| 
 | ||||
| 			servicePeers = append(servicePeers, adminPeer{ | ||||
| 				addr:      ep.Host, | ||||
| 				cmdRunner: &remoteAdminClient{newAuthRPCClient(cfg)}, | ||||
| 			})}, | ||||
| 		}) | ||||
| 			seenAddr[ep.Host] = true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return servicePeers | ||||
| 	return adminPeerList | ||||
| } | ||||
| 
 | ||||
| // Initialize global adminPeer collection. | ||||
| func initGlobalAdminPeers(eps []*url.URL) { | ||||
| 	globalAdminPeers = makeAdminPeers(eps) | ||||
| func initGlobalAdminPeers(endpoints EndpointList) { | ||||
| 	globalAdminPeers = makeAdminPeers(endpoints) | ||||
| } | ||||
| 
 | ||||
| // invokeServiceCmd - Invoke Restart command. | ||||
|  | ||||
| @ -18,7 +18,6 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"net/url" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| @ -86,9 +85,7 @@ func TestReInitDisks(t *testing.T) { | ||||
| 	defer removeRoots(xlDirs) | ||||
| 
 | ||||
| 	// Set globalEndpoints for a single node XL setup. | ||||
| 	for _, xlDir := range xlDirs { | ||||
| 		globalEndpoints = append(globalEndpoints, &url.URL{Path: xlDir}) | ||||
| 	} | ||||
| 	globalEndpoints = mustGetNewEndpointList(xlDirs...) | ||||
| 
 | ||||
| 	// Setup admin rpc server for an XL backend. | ||||
| 	globalIsXL = true | ||||
|  | ||||
| @ -16,7 +16,11 @@ | ||||
| 
 | ||||
| package cmd | ||||
| 
 | ||||
| import "strings" | ||||
| import ( | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/set" | ||||
| ) | ||||
| 
 | ||||
| // List of valid event types. | ||||
| var suppportedEventTypes = map[string]struct{}{ | ||||
| @ -207,16 +211,14 @@ func validateQueueConfigs(queueConfigs []queueConfig) APIErrorCode { | ||||
| 
 | ||||
| // Check all the queue configs for any duplicates. | ||||
| func checkDuplicateQueueConfigs(configs []queueConfig) APIErrorCode { | ||||
| 	var queueConfigARNS []string | ||||
| 	queueConfigARNS := set.NewStringSet() | ||||
| 
 | ||||
| 	// Navigate through each configs and count the entries. | ||||
| 	for _, config := range configs { | ||||
| 		queueConfigARNS = append(queueConfigARNS, config.QueueARN) | ||||
| 		queueConfigARNS.Add(config.QueueARN) | ||||
| 	} | ||||
| 
 | ||||
| 	// Check if there are any duplicate counts. | ||||
| 	if err := checkDuplicateStrings(queueConfigARNS); err != nil { | ||||
| 		errorIf(err, "Invalid queue configs found.") | ||||
| 	if len(queueConfigARNS) != len(configs) { | ||||
| 		return ErrOverlappingConfigs | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -1,64 +0,0 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2016 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 ( | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
| ) | ||||
| 
 | ||||
| // checkPortAvailability - check if given port is already in use. | ||||
| // Note: The check method tries to listen on given port and closes it. | ||||
| // It is possible to have a disconnected client in this tiny window of time. | ||||
| func checkPortAvailability(port string) error { | ||||
| 	network := [3]string{"tcp", "tcp4", "tcp6"} | ||||
| 	for _, n := range network { | ||||
| 		l, err := net.Listen(n, net.JoinHostPort("", port)) | ||||
| 		if err != nil { | ||||
| 			if isAddrInUse(err) { | ||||
| 				// Return error if another process is listening on the | ||||
| 				// same port. | ||||
| 				return err | ||||
| 			} | ||||
| 			// Ignore any other error (ex. EAFNOSUPPORT) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// look for error so we don't have dangling connection | ||||
| 		if err = l.Close(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Return true if err is "address already in use" error. | ||||
| // syscall.EADDRINUSE is available on all OSes. | ||||
| func isAddrInUse(err error) bool { | ||||
| 	if opErr, ok := err.(*net.OpError); ok { | ||||
| 		if sysErr, ok := opErr.Err.(*os.SyscallError); ok { | ||||
| 			if errno, ok := sysErr.Err.(syscall.Errno); ok { | ||||
| 				if errno == syscall.EADDRINUSE { | ||||
| 					return true | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| @ -1,54 +0,0 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2016, 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 ( | ||||
| 	"net" | ||||
| 	"runtime" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| // Tests for port availability logic written for server startup sequence. | ||||
| func TestCheckPortAvailability(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		port string | ||||
| 	}{ | ||||
| 		{getFreePort()}, | ||||
| 		{getFreePort()}, | ||||
| 	} | ||||
| 	for _, test := range tests { | ||||
| 		// This test should pass if the ports are available | ||||
| 		err := checkPortAvailability(test.port) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("checkPortAvailability test failed for port: %s. Error: %v", test.port, err) | ||||
| 		} | ||||
| 
 | ||||
| 		// Now use the ports and check again | ||||
| 		ln, err := net.Listen("tcp", net.JoinHostPort("", test.port)) | ||||
| 		if err != nil { | ||||
| 			t.Fail() | ||||
| 		} | ||||
| 		defer ln.Close() | ||||
| 
 | ||||
| 		err = checkPortAvailability(test.port) | ||||
| 
 | ||||
| 		// Skip if the os is windows due to https://github.com/golang/go/issues/7598 | ||||
| 		if err == nil && runtime.GOOS != globalWindowsOSName { | ||||
| 			t.Fatalf("checkPortAvailability should fail for port: %s. Error: %v", test.port, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										382
									
								
								cmd/endpoint.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								cmd/endpoint.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,382 @@ | ||||
| /* | ||||
|  * 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 ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/set" | ||||
| ) | ||||
| 
 | ||||
| // EndpointType - enum for endpoint type. | ||||
| type EndpointType int | ||||
| 
 | ||||
| const ( | ||||
| 	// PathEndpointType - path style endpoint type enum. | ||||
| 	PathEndpointType EndpointType = iota + 1 | ||||
| 
 | ||||
| 	// URLEndpointType - URL style endpoint type enum. | ||||
| 	URLEndpointType | ||||
| ) | ||||
| 
 | ||||
| // Endpoint - any type of endpoint. | ||||
| type Endpoint struct { | ||||
| 	*url.URL | ||||
| 	IsLocal bool | ||||
| } | ||||
| 
 | ||||
| func (endpoint Endpoint) String() string { | ||||
| 	if endpoint.Host == "" { | ||||
| 		return endpoint.Path | ||||
| 	} | ||||
| 
 | ||||
| 	return endpoint.URL.String() | ||||
| } | ||||
| 
 | ||||
| // Type - returns type of endpoint. | ||||
| func (endpoint Endpoint) Type() EndpointType { | ||||
| 	if endpoint.Host == "" { | ||||
| 		return PathEndpointType | ||||
| 	} | ||||
| 
 | ||||
| 	return URLEndpointType | ||||
| } | ||||
| 
 | ||||
| // SetHTTPS - sets secure http for URLEndpointType. | ||||
| func (endpoint Endpoint) SetHTTPS() { | ||||
| 	if endpoint.Host != "" { | ||||
| 		endpoint.Scheme = "https" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // SetHTTP - sets insecure http for URLEndpointType. | ||||
| func (endpoint Endpoint) SetHTTP() { | ||||
| 	if endpoint.Host != "" { | ||||
| 		endpoint.Scheme = "http" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NewEndpoint - returns new endpoint based on given arguments. | ||||
| func NewEndpoint(arg string) (Endpoint, error) { | ||||
| 	// isEmptyPath - check whether given path is not empty. | ||||
| 	isEmptyPath := func(path string) bool { | ||||
| 		return path == "" || path == "." || path == "/" || path == `\` | ||||
| 	} | ||||
| 
 | ||||
| 	if isEmptyPath(arg) { | ||||
| 		return Endpoint{}, fmt.Errorf("empty or root endpoint is not supported") | ||||
| 	} | ||||
| 
 | ||||
| 	var isLocal bool | ||||
| 	u, err := url.Parse(arg) | ||||
| 	if err == nil && u.Host != "" { | ||||
| 		// URL style of endpoint. | ||||
| 		// Valid URL style endpoint is | ||||
| 		// - Scheme field must contain "http" or "https" | ||||
| 		// - All field should be empty except Host and Path. | ||||
| 		if !((u.Scheme == "http" || u.Scheme == "https") && | ||||
| 			u.User == nil && u.Opaque == "" && u.ForceQuery == false && u.RawQuery == "" && u.Fragment == "") { | ||||
| 			return Endpoint{}, fmt.Errorf("invalid URL endpoint format") | ||||
| 		} | ||||
| 
 | ||||
| 		host, port, err := net.SplitHostPort(u.Host) | ||||
| 		if err != nil { | ||||
| 			if !strings.Contains(err.Error(), "missing port in address") { | ||||
| 				return Endpoint{}, fmt.Errorf("invalid URL endpoint format: %s", err) | ||||
| 			} | ||||
| 
 | ||||
| 			host = u.Host | ||||
| 		} else { | ||||
| 			var p int | ||||
| 			p, err = strconv.Atoi(port) | ||||
| 			if err != nil { | ||||
| 				return Endpoint{}, fmt.Errorf("invalid URL endpoint format: invalid port number") | ||||
| 			} else if p < 1 || p > 65535 { | ||||
| 				return Endpoint{}, fmt.Errorf("invalid URL endpoint format: port number must be between 1 to 65535") | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if host == "" { | ||||
| 			return Endpoint{}, fmt.Errorf("invalid URL endpoint format: empty host name") | ||||
| 		} | ||||
| 
 | ||||
| 		// As this is path in the URL, we should use path package, not filepath package. | ||||
| 		// On MS Windows, filepath.Clean() converts into Windows path style ie `/foo` becomes `\foo` | ||||
| 		u.Path = path.Clean(u.Path) | ||||
| 		if isEmptyPath(u.Path) { | ||||
| 			return Endpoint{}, fmt.Errorf("empty or root path is not supported in URL endpoint") | ||||
| 		} | ||||
| 
 | ||||
| 		// Get IPv4 address of the host. | ||||
| 		hostIPs, err := getHostIP4(host) | ||||
| 		if err != nil { | ||||
| 			return Endpoint{}, err | ||||
| 		} | ||||
| 
 | ||||
| 		// If intersection of two IP sets is not empty, then the host is local host. | ||||
| 		isLocal = !localIP4.Intersection(hostIPs).IsEmpty() | ||||
| 	} else { | ||||
| 		u = &url.URL{Path: path.Clean(arg)} | ||||
| 		isLocal = true | ||||
| 	} | ||||
| 
 | ||||
| 	return Endpoint{ | ||||
| 		URL:     u, | ||||
| 		IsLocal: isLocal, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // EndpointList - list of same type of endpoint. | ||||
| type EndpointList []Endpoint | ||||
| 
 | ||||
| // Swap - helper method for sorting. | ||||
| func (endpoints EndpointList) Swap(i, j int) { | ||||
| 	endpoints[i], endpoints[j] = endpoints[j], endpoints[i] | ||||
| } | ||||
| 
 | ||||
| // Len - helper method for sorting. | ||||
| func (endpoints EndpointList) Len() int { | ||||
| 	return len(endpoints) | ||||
| } | ||||
| 
 | ||||
| // Less - helper method for sorting. | ||||
| func (endpoints EndpointList) Less(i, j int) bool { | ||||
| 	return endpoints[i].String() < endpoints[j].String() | ||||
| } | ||||
| 
 | ||||
| // SetHTTPS - sets secure http for URLEndpointType. | ||||
| func (endpoints EndpointList) SetHTTPS() { | ||||
| 	for i := range endpoints { | ||||
| 		endpoints[i].SetHTTPS() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // SetHTTP - sets insecure http for URLEndpointType. | ||||
| func (endpoints EndpointList) SetHTTP() { | ||||
| 	for i := range endpoints { | ||||
| 		endpoints[i].SetHTTP() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NewEndpointList - returns new endpoint list based on input args. | ||||
| func NewEndpointList(args ...string) (endpoints EndpointList, err error) { | ||||
| 	// isValidDistribution - checks whether given count is a valid distribution for erasure coding. | ||||
| 	isValidDistribution := func(count int) bool { | ||||
| 		return (count >= 4 && count <= 16 && count%2 == 0) | ||||
| 	} | ||||
| 
 | ||||
| 	// Check whether no. of args are valid for XL distribution. | ||||
| 	if !isValidDistribution(len(args)) { | ||||
| 		return nil, fmt.Errorf("total endpoints %d found. For XL/Distribute, it should be 4, 6, 8, 10, 12, 14 or 16", len(args)) | ||||
| 	} | ||||
| 
 | ||||
| 	var endpointType EndpointType | ||||
| 	var scheme string | ||||
| 
 | ||||
| 	uniqueArgs := set.NewStringSet() | ||||
| 	// Loop through args and adds to endpoint list. | ||||
| 	for i, arg := range args { | ||||
| 		endpoint, err := NewEndpoint(arg) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("'%s': %s", arg, err.Error()) | ||||
| 		} | ||||
| 
 | ||||
| 		// All endpoints have to be same type and scheme if applicable. | ||||
| 		if i == 0 { | ||||
| 			endpointType = endpoint.Type() | ||||
| 			scheme = endpoint.Scheme | ||||
| 		} else if endpoint.Type() != endpointType { | ||||
| 			return nil, fmt.Errorf("mixed style endpoints are not supported") | ||||
| 		} else if endpoint.Scheme != scheme { | ||||
| 			return nil, fmt.Errorf("mixed scheme is not supported") | ||||
| 		} | ||||
| 
 | ||||
| 		arg = endpoint.String() | ||||
| 		if uniqueArgs.Contains(arg) { | ||||
| 			return nil, fmt.Errorf("duplicate endpoints found") | ||||
| 		} | ||||
| 		uniqueArgs.Add(arg) | ||||
| 
 | ||||
| 		endpoints = append(endpoints, endpoint) | ||||
| 	} | ||||
| 
 | ||||
| 	sort.Sort(endpoints) | ||||
| 
 | ||||
| 	return endpoints, nil | ||||
| } | ||||
| 
 | ||||
| // CreateEndpoints - validates and creates new endpoints for given args. | ||||
| func CreateEndpoints(serverAddr string, args ...string) (string, EndpointList, SetupType, error) { | ||||
| 	var endpoints EndpointList | ||||
| 	var setupType SetupType | ||||
| 	var err error | ||||
| 
 | ||||
| 	// Check whether serverAddr is valid for this host. | ||||
| 	if err = CheckLocalServerAddr(serverAddr); err != nil { | ||||
| 		return serverAddr, endpoints, setupType, err | ||||
| 	} | ||||
| 
 | ||||
| 	_, serverAddrPort := mustSplitHostPort(serverAddr) | ||||
| 
 | ||||
| 	// For single arg, return FS setup. | ||||
| 	if len(args) == 1 { | ||||
| 		var endpoint Endpoint | ||||
| 		endpoint, err = NewEndpoint(args[0]) | ||||
| 		if err != nil { | ||||
| 			return serverAddr, endpoints, setupType, err | ||||
| 		} | ||||
| 
 | ||||
| 		if endpoint.Type() != PathEndpointType { | ||||
| 			return serverAddr, endpoints, setupType, fmt.Errorf("use path style endpoint for FS setup") | ||||
| 		} | ||||
| 
 | ||||
| 		endpoints = append(endpoints, endpoint) | ||||
| 		setupType = FSSetupType | ||||
| 		return serverAddr, endpoints, setupType, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Convert args to endpoints | ||||
| 	if endpoints, err = NewEndpointList(args...); err != nil { | ||||
| 		return serverAddr, endpoints, setupType, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Return XL setup when all endpoints are path style. | ||||
| 	if endpoints[0].Type() == PathEndpointType { | ||||
| 		setupType = XLSetupType | ||||
| 		return serverAddr, endpoints, setupType, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Here all endpoints are URL style. | ||||
| 	endpointPathSet := set.NewStringSet() | ||||
| 	localEndpointCount := 0 | ||||
| 	localServerAddrSet := set.NewStringSet() | ||||
| 	localPortSet := set.NewStringSet() | ||||
| 	for _, endpoint := range endpoints { | ||||
| 		endpointPathSet.Add(endpoint.Path) | ||||
| 		if endpoint.IsLocal { | ||||
| 			localServerAddrSet.Add(endpoint.Host) | ||||
| 
 | ||||
| 			var port string | ||||
| 			_, port, err = net.SplitHostPort(endpoint.Host) | ||||
| 			if err != nil { | ||||
| 				port = serverAddrPort | ||||
| 			} | ||||
| 
 | ||||
| 			localPortSet.Add(port) | ||||
| 
 | ||||
| 			localEndpointCount++ | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// No local endpoint found. | ||||
| 	if localEndpointCount == 0 { | ||||
| 		return serverAddr, endpoints, setupType, fmt.Errorf("no endpoint found for this host") | ||||
| 	} | ||||
| 
 | ||||
| 	// Check whether same path is not used in endpoints of a host. | ||||
| 	{ | ||||
| 		pathIPMap := make(map[string]set.StringSet) | ||||
| 		for _, endpoint := range endpoints { | ||||
| 			var host string | ||||
| 			host, _, err = net.SplitHostPort(endpoint.Host) | ||||
| 			if err != nil { | ||||
| 				host = endpoint.Host | ||||
| 			} | ||||
| 			hostIPSet, _ := getHostIP4(host) | ||||
| 			if IPSet, ok := pathIPMap[endpoint.Path]; ok { | ||||
| 				if !IPSet.Intersection(hostIPSet).IsEmpty() { | ||||
| 					err = fmt.Errorf("path '%s' can not be served from different address/port", endpoint.Path) | ||||
| 					return serverAddr, endpoints, setupType, err | ||||
| 				} | ||||
| 			} else { | ||||
| 				pathIPMap[endpoint.Path] = hostIPSet | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Check whether serverAddrPort matches at least in one of port used in local endpoints. | ||||
| 	{ | ||||
| 		if !localPortSet.Contains(serverAddrPort) { | ||||
| 			if len(localPortSet) > 1 { | ||||
| 				err = fmt.Errorf("port number in server address must match with one of the port in local endpoints") | ||||
| 			} else { | ||||
| 				err = fmt.Errorf("server address and local endpoint have different ports") | ||||
| 			} | ||||
| 
 | ||||
| 			return serverAddr, endpoints, setupType, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// If all endpoints are pointing to local host and having same port number, then this is XL setup using URL style endpoints. | ||||
| 	if len(endpoints) == localEndpointCount && len(localPortSet) == 1 { | ||||
| 		if len(localServerAddrSet) > 1 { | ||||
| 			// TODO: Eventhough all endpoints are local, the local host is referred by different IP/name. | ||||
| 			// eg '172.0.0.1', 'localhost' and 'mylocalhostname' point to same local host. | ||||
| 			// | ||||
| 			// In this case, we bind to 0.0.0.0 ie to all interfaces. | ||||
| 			// The actual way to do is bind to only IPs in uniqueLocalHosts. | ||||
| 			serverAddr = net.JoinHostPort("", serverAddrPort) | ||||
| 		} | ||||
| 
 | ||||
| 		endpointPaths := endpointPathSet.ToSlice() | ||||
| 		endpoints, _ = NewEndpointList(endpointPaths...) | ||||
| 		setupType = XLSetupType | ||||
| 		return serverAddr, endpoints, setupType, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Add missing port in all endpoints. | ||||
| 	for i := range endpoints { | ||||
| 		_, port, err := net.SplitHostPort(endpoints[i].Host) | ||||
| 		if err != nil { | ||||
| 			endpoints[i].Host = net.JoinHostPort(endpoints[i].Host, serverAddrPort) | ||||
| 		} else if endpoints[i].IsLocal && serverAddrPort != port { | ||||
| 			// If endpoint is local, but port is different than serverAddrPort, then make it as remote. | ||||
| 			endpoints[i].IsLocal = false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// This is DistXL setup. | ||||
| 	setupType = DistXLSetupType | ||||
| 	return serverAddr, endpoints, setupType, nil | ||||
| } | ||||
| 
 | ||||
| // GetRemotePeers - get hosts information other than this minio service. | ||||
| func GetRemotePeers(endpoints EndpointList) []string { | ||||
| 	peerSet := set.NewStringSet() | ||||
| 	for _, endpoint := range endpoints { | ||||
| 		if endpoint.Type() != URLEndpointType { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		peer := endpoint.Host | ||||
| 		if endpoint.IsLocal { | ||||
| 			if _, port := mustSplitHostPort(peer); port == globalMinioPort { | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		peerSet.Add(peer) | ||||
| 	} | ||||
| 
 | ||||
| 	return peerSet.ToSlice() | ||||
| } | ||||
							
								
								
									
										312
									
								
								cmd/endpoint_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								cmd/endpoint_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,312 @@ | ||||
| /* | ||||
|  * 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 ( | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"reflect" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestNewEndpoint(t *testing.T) { | ||||
| 	u1, _ := url.Parse("http://localhost/path") | ||||
| 	u2, _ := url.Parse("https://example.org/path") | ||||
| 	u3, _ := url.Parse("http://127.0.0.1:8080/path") | ||||
| 	u4, _ := url.Parse("http://192.168.253.200/path") | ||||
| 
 | ||||
| 	errMsg := ": no such host" | ||||
| 	if runtime.GOOS == "windows" { | ||||
| 		errMsg = ": No such host is known." | ||||
| 	} | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		arg              string | ||||
| 		expectedEndpoint Endpoint | ||||
| 		expectedType     EndpointType | ||||
| 		expectedErr      error | ||||
| 	}{ | ||||
| 		{"foo", Endpoint{URL: &url.URL{Path: "foo"}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"/foo", Endpoint{URL: &url.URL{Path: "/foo"}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{`\foo`, Endpoint{URL: &url.URL{Path: `\foo`}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"C", Endpoint{URL: &url.URL{Path: `C`}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"C:", Endpoint{URL: &url.URL{Path: `C:`}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"C:/", Endpoint{URL: &url.URL{Path: "C:"}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{`C:\`, Endpoint{URL: &url.URL{Path: `C:\`}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{`C:\foo`, Endpoint{URL: &url.URL{Path: `C:\foo`}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"C:/foo", Endpoint{URL: &url.URL{Path: "C:/foo"}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{`C:\\foo`, Endpoint{URL: &url.URL{Path: `C:\\foo`}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"http:path", Endpoint{URL: &url.URL{Path: "http:path"}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"http:/path", Endpoint{URL: &url.URL{Path: "http:/path"}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"http:///path", Endpoint{URL: &url.URL{Path: "http:/path"}, IsLocal: true}, PathEndpointType, nil}, | ||||
| 		{"http://localhost/path", Endpoint{URL: u1, IsLocal: true}, URLEndpointType, nil}, | ||||
| 		{"http://localhost/path//", Endpoint{URL: u1, IsLocal: true}, URLEndpointType, nil}, | ||||
| 		{"https://example.org/path", Endpoint{URL: u2}, URLEndpointType, nil}, | ||||
| 		{"http://127.0.0.1:8080/path", Endpoint{URL: u3, IsLocal: true}, URLEndpointType, nil}, | ||||
| 		{"http://192.168.253.200/path", Endpoint{URL: u4}, URLEndpointType, nil}, | ||||
| 		{"", Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")}, | ||||
| 		{".", Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")}, | ||||
| 		{"/", Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")}, | ||||
| 		{`\`, Endpoint{}, -1, fmt.Errorf("empty or root endpoint is not supported")}, | ||||
| 		{"c://foo", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format")}, | ||||
| 		{"ftp://foo", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format")}, | ||||
| 		{"http://server/path?location", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format")}, | ||||
| 		{"http://:/path", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: invalid port number")}, | ||||
| 		{"http://:8080/path", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: empty host name")}, | ||||
| 		{"http://server:/path", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: invalid port number")}, | ||||
| 		{"https://93.184.216.34:808080/path", Endpoint{}, -1, fmt.Errorf("invalid URL endpoint format: port number must be between 1 to 65535")}, | ||||
| 		{"http://server:8080//", Endpoint{}, -1, fmt.Errorf("empty or root path is not supported in URL endpoint")}, | ||||
| 		{"http://server:8080/", Endpoint{}, -1, fmt.Errorf("empty or root path is not supported in URL endpoint")}, | ||||
| 		{"http://server/path", Endpoint{}, -1, fmt.Errorf("lookup server" + errMsg)}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, testCase := range testCases { | ||||
| 		endpoint, err := NewEndpoint(testCase.arg) | ||||
| 		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 { | ||||
| 			match := false | ||||
| 			if strings.HasSuffix(testCase.expectedErr.Error(), errMsg) { | ||||
| 				match = strings.HasSuffix(err.Error(), errMsg) | ||||
| 			} else { | ||||
| 				match = (testCase.expectedErr.Error() == err.Error()) | ||||
| 			} | ||||
| 			if !match { | ||||
| 				t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if err == nil && !reflect.DeepEqual(testCase.expectedEndpoint, endpoint) { | ||||
| 			t.Fatalf("endpoint: expected = %+v, got = %+v", testCase.expectedEndpoint, endpoint) | ||||
| 		} | ||||
| 
 | ||||
| 		if err == nil && testCase.expectedType != endpoint.Type() { | ||||
| 			t.Fatalf("type: expected = %+v, got = %+v", testCase.expectedType, endpoint.Type()) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestNewEndpointList(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		args        []string | ||||
| 		expectedErr error | ||||
| 	}{ | ||||
| 		{[]string{"d1", "d2", "d3", "d4"}, nil}, | ||||
| 		{[]string{"/d1", "/d2", "/d3", "/d4"}, nil}, | ||||
| 		{[]string{"http://localhost/d1", "http://localhost/d2", "http://localhost/d3", "http://localhost/d4"}, nil}, | ||||
| 		{[]string{"http://example.org/d1", "http://example.com/d1", "http://example.net/d1", "http://example.edu/d1"}, nil}, | ||||
| 		{[]string{"http://localhost/d1", "http://localhost/d2", "http://example.org/d1", "http://example.org/d2"}, nil}, | ||||
| 		{[]string{"https://localhost:9000/d1", "https://localhost:9001/d2", "https://localhost:9002/d3", "https://localhost:9003/d4"}, nil}, | ||||
| 		// // It is valid WRT endpoint list that same path is expected with different port on same server. | ||||
| 		{[]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}, | ||||
| 		{[]string{"d1", "d2", "d3", "d1"}, fmt.Errorf("duplicate endpoints found")}, | ||||
| 		{[]string{"d1", "d2", "d3", "./d1"}, fmt.Errorf("duplicate endpoints found")}, | ||||
| 		{[]string{"http://localhost/d1", "http://localhost/d2", "http://localhost/d1", "http://localhost/d4"}, fmt.Errorf("duplicate endpoints found")}, | ||||
| 		{[]string{"d1", "d2", "d3", "d4", "d5"}, fmt.Errorf("total endpoints 5 found. For XL/Distribute, it should be 4, 6, 8, 10, 12, 14 or 16")}, | ||||
| 		{[]string{"ftp://server/d1", "http://server/d2", "http://server/d3", "http://server/d4"}, fmt.Errorf("'ftp://server/d1': invalid URL endpoint format")}, | ||||
| 		{[]string{"d1", "http://localhost/d2", "d3", "d4"}, fmt.Errorf("mixed style endpoints are not supported")}, | ||||
| 		{[]string{"http://example.org/d1", "https://example.com/d1", "http://example.net/d1", "https://example.edut/d1"}, fmt.Errorf("mixed scheme is not supported")}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, testCase := range testCases { | ||||
| 		_, err := NewEndpointList(testCase.args...) | ||||
| 		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 TestCreateEndpoints(t *testing.T) { | ||||
| 	case1u1, _ := url.Parse("http://example.com:10000/d4") | ||||
| 	case1u2, _ := url.Parse("http://example.org:10000/d3") | ||||
| 	case1u3, _ := url.Parse("http://localhost:10000/d1") | ||||
| 	case1u4, _ := url.Parse("http://localhost:10000/d2") | ||||
| 
 | ||||
| 	case2u1, _ := url.Parse("http://example.com:10000/d4") | ||||
| 	case2u2, _ := url.Parse("http://example.org:10000/d3") | ||||
| 	case2u3, _ := url.Parse("http://localhost:10000/d1") | ||||
| 	case2u4, _ := url.Parse("http://localhost:9000/d2") | ||||
| 
 | ||||
| 	case3u1, _ := url.Parse("http://example.com:80/d3") | ||||
| 	case3u2, _ := url.Parse("http://example.net:80/d4") | ||||
| 	case3u3, _ := url.Parse("http://example.org:9000/d2") | ||||
| 	case3u4, _ := url.Parse("http://localhost:80/d1") | ||||
| 
 | ||||
| 	case4u1, _ := url.Parse("http://example.com:9000/d3") | ||||
| 	case4u2, _ := url.Parse("http://example.net:9000/d4") | ||||
| 	case4u3, _ := url.Parse("http://example.org:9000/d2") | ||||
| 	case4u4, _ := url.Parse("http://localhost:9000/d1") | ||||
| 
 | ||||
| 	case5u1, _ := url.Parse("http://localhost:9000/d1") | ||||
| 	case5u2, _ := url.Parse("http://localhost:9001/d2") | ||||
| 	case5u3, _ := url.Parse("http://localhost:9002/d3") | ||||
| 	case5u4, _ := url.Parse("http://localhost:9003/d4") | ||||
| 
 | ||||
| 	case6u1, _ := url.Parse("http://10.0.0.1:9000/export") | ||||
| 	case6u2, _ := url.Parse("http://10.0.0.2:9000/export") | ||||
| 	case6u3, _ := url.Parse("http://10.0.0.3:9000/export") | ||||
| 	case6u4, _ := url.Parse("http://localhost:9001/export") | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		serverAddr         string | ||||
| 		args               []string | ||||
| 		expectedServerAddr string | ||||
| 		expectedEndpoints  EndpointList | ||||
| 		expectedSetupType  SetupType | ||||
| 		expectedErr        error | ||||
| 	}{ | ||||
| 		{"localhost", []string{}, "", EndpointList{}, -1, fmt.Errorf("missing port in address localhost")}, | ||||
| 
 | ||||
| 		// FS Setup | ||||
| 		{"localhost:9000", []string{"http://localhost/d1"}, "", EndpointList{}, -1, fmt.Errorf("use path style endpoint for FS setup")}, | ||||
| 		{":443", []string{"d1"}, ":443", EndpointList{Endpoint{URL: &url.URL{Path: "d1"}, IsLocal: true}}, FSSetupType, nil}, | ||||
| 		{"localhost:10000", []string{"/d1"}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: "/d1"}, IsLocal: true}}, FSSetupType, nil}, | ||||
| 		{"localhost:10000", []string{"./d1"}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: "d1"}, IsLocal: true}}, FSSetupType, nil}, | ||||
| 		{"localhost:10000", []string{`\d1`}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: `\d1`}, IsLocal: true}}, FSSetupType, nil}, | ||||
| 		{"localhost:10000", []string{`.\d1`}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: `.\d1`}, IsLocal: true}}, FSSetupType, nil}, | ||||
| 		{":8080", []string{"https://example.org/d1", "https://example.org/d2", "https://example.org/d3", "https://example.org/d4"}, "", EndpointList{}, -1, fmt.Errorf("no endpoint found for this host")}, | ||||
| 		{":8080", []string{"https://example.org/d1", "https://example.com/d2", "https://example.net:8000/d3", "https://example.edu/d1"}, "", EndpointList{}, -1, fmt.Errorf("no endpoint found for this host")}, | ||||
| 		{"localhost:9000", []string{"https://127.0.0.1:9000/d1", "https://localhost:9001/d1", "https://example.com/d1", "https://example.com/d2"}, "", EndpointList{}, -1, fmt.Errorf("path '/d1' can not be served from different address/port")}, | ||||
| 		{"localhost:9000", []string{"https://127.0.0.1:8000/d1", "https://localhost:9001/d2", "https://example.com/d1", "https://example.com/d2"}, "", EndpointList{}, -1, fmt.Errorf("port number in server address must match with one of the port in local endpoints")}, | ||||
| 		{"localhost:10000", []string{"https://127.0.0.1:8000/d1", "https://localhost:8000/d2", "https://example.com/d1", "https://example.com/d2"}, "", EndpointList{}, -1, fmt.Errorf("server address and local endpoint have different ports")}, | ||||
| 
 | ||||
| 		// XL Setup with PathEndpointType | ||||
| 		{":1234", []string{"/d1", "/d2", "d3", "d4"}, ":1234", | ||||
| 			EndpointList{ | ||||
| 				Endpoint{URL: &url.URL{Path: "/d1"}, IsLocal: true}, | ||||
| 				Endpoint{URL: &url.URL{Path: "/d2"}, IsLocal: true}, | ||||
| 				Endpoint{URL: &url.URL{Path: "d3"}, IsLocal: true}, | ||||
| 				Endpoint{URL: &url.URL{Path: "d4"}, IsLocal: true}, | ||||
| 			}, XLSetupType, nil}, | ||||
| 		// XL Setup with URLEndpointType | ||||
| 		{":9000", []string{"http://localhost/d1", "http://localhost/d2", "http://localhost/d3", "http://localhost/d4"}, ":9000", EndpointList{ | ||||
| 			Endpoint{URL: &url.URL{Path: "/d1"}, IsLocal: true}, | ||||
| 			Endpoint{URL: &url.URL{Path: "/d2"}, IsLocal: true}, | ||||
| 			Endpoint{URL: &url.URL{Path: "/d3"}, IsLocal: true}, | ||||
| 			Endpoint{URL: &url.URL{Path: "/d4"}, IsLocal: true}, | ||||
| 		}, XLSetupType, nil}, | ||||
| 		// XL Setup with URLEndpointType having mixed naming to local host. | ||||
| 		{"127.0.0.1:10000", []string{"http://localhost/d1", "http://localhost/d2", "http://127.0.0.1/d3", "http://127.0.0.1/d4"}, ":10000", EndpointList{ | ||||
| 			Endpoint{URL: &url.URL{Path: "/d1"}, IsLocal: true}, | ||||
| 			Endpoint{URL: &url.URL{Path: "/d2"}, IsLocal: true}, | ||||
| 			Endpoint{URL: &url.URL{Path: "/d3"}, IsLocal: true}, | ||||
| 			Endpoint{URL: &url.URL{Path: "/d4"}, IsLocal: true}, | ||||
| 		}, XLSetupType, nil}, | ||||
| 
 | ||||
| 		// DistXL type | ||||
| 		{"127.0.0.1:10000", []string{"http://localhost/d1", "http://localhost/d2", "http://example.org/d3", "http://example.com/d4"}, "127.0.0.1:10000", EndpointList{ | ||||
| 			Endpoint{URL: case1u1, IsLocal: false}, | ||||
| 			Endpoint{URL: case1u2, IsLocal: false}, | ||||
| 			Endpoint{URL: case1u3, IsLocal: true}, | ||||
| 			Endpoint{URL: case1u4, IsLocal: true}, | ||||
| 		}, DistXLSetupType, nil}, | ||||
| 		{"127.0.0.1:10000", []string{"http://localhost/d1", "http://localhost:9000/d2", "http://example.org/d3", "http://example.com/d4"}, "127.0.0.1:10000", EndpointList{ | ||||
| 			Endpoint{URL: case2u1, IsLocal: false}, | ||||
| 			Endpoint{URL: case2u2, IsLocal: false}, | ||||
| 			Endpoint{URL: case2u3, IsLocal: true}, | ||||
| 			Endpoint{URL: case2u4, IsLocal: false}, | ||||
| 		}, DistXLSetupType, nil}, | ||||
| 		{":80", []string{"http://localhost/d1", "http://example.org:9000/d2", "http://example.com/d3", "http://example.net/d4"}, ":80", EndpointList{ | ||||
| 			Endpoint{URL: case3u1, IsLocal: false}, | ||||
| 			Endpoint{URL: case3u2, IsLocal: false}, | ||||
| 			Endpoint{URL: case3u3, IsLocal: false}, | ||||
| 			Endpoint{URL: case3u4, IsLocal: true}, | ||||
| 		}, DistXLSetupType, nil}, | ||||
| 		{":9000", []string{"http://localhost/d1", "http://example.org/d2", "http://example.com/d3", "http://example.net/d4"}, ":9000", EndpointList{ | ||||
| 			Endpoint{URL: case4u1, IsLocal: false}, | ||||
| 			Endpoint{URL: case4u2, IsLocal: false}, | ||||
| 			Endpoint{URL: case4u3, IsLocal: false}, | ||||
| 			Endpoint{URL: case4u4, IsLocal: true}, | ||||
| 		}, DistXLSetupType, nil}, | ||||
| 		{":9000", []string{"http://localhost:9000/d1", "http://localhost:9001/d2", "http://localhost:9002/d3", "http://localhost:9003/d4"}, ":9000", EndpointList{ | ||||
| 			Endpoint{URL: case5u1, IsLocal: true}, | ||||
| 			Endpoint{URL: case5u2, IsLocal: false}, | ||||
| 			Endpoint{URL: case5u3, IsLocal: false}, | ||||
| 			Endpoint{URL: case5u4, IsLocal: false}, | ||||
| 		}, DistXLSetupType, nil}, | ||||
| 
 | ||||
| 		{":9001", []string{"http://10.0.0.1:9000/export", "http://10.0.0.2:9000/export", "http://localhost:9001/export", "http://10.0.0.3:9000/export"}, ":9001", EndpointList{ | ||||
| 			Endpoint{URL: case6u1, IsLocal: false}, | ||||
| 			Endpoint{URL: case6u2, IsLocal: false}, | ||||
| 			Endpoint{URL: case6u3, IsLocal: false}, | ||||
| 			Endpoint{URL: case6u4, IsLocal: true}, | ||||
| 		}, DistXLSetupType, nil}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, testCase := range testCases { | ||||
| 		serverAddr, endpoints, setupType, err := CreateEndpoints(testCase.serverAddr, testCase.args...) | ||||
| 
 | ||||
| 		if err == nil { | ||||
| 			if testCase.expectedErr != nil { | ||||
| 				t.Fatalf("error: expected = %v, got = <nil>", testCase.expectedErr) | ||||
| 			} else { | ||||
| 				if serverAddr != testCase.expectedServerAddr { | ||||
| 					t.Fatalf("serverAddr: expected = %v, got = %v", testCase.expectedServerAddr, serverAddr) | ||||
| 				} | ||||
| 				if !reflect.DeepEqual(endpoints, testCase.expectedEndpoints) { | ||||
| 					t.Fatalf("endpoints: expected = %v, got = %v", testCase.expectedEndpoints, endpoints) | ||||
| 				} | ||||
| 				if setupType != testCase.expectedSetupType { | ||||
| 					t.Fatalf("setupType: expected = %v, got = %v", testCase.expectedSetupType, setupType) | ||||
| 				} | ||||
| 			} | ||||
| 		} else if testCase.expectedErr == nil { | ||||
| 			t.Fatalf("error: expected = <nil>, got = %v", err) | ||||
| 		} else if err.Error() != testCase.expectedErr.Error() { | ||||
| 			t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestGetRemotePeers(t *testing.T) { | ||||
| 	tempGlobalMinioPort := globalMinioPort | ||||
| 	defer func() { | ||||
| 		globalMinioPort = tempGlobalMinioPort | ||||
| 	}() | ||||
| 	globalMinioPort = "9000" | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		endpointArgs   []string | ||||
| 		expectedResult []string | ||||
| 	}{ | ||||
| 		{[]string{"/d1", "/d2", "d3", "d4"}, []string{}}, | ||||
| 		{[]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"}}, | ||||
| 		{[]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"}}, | ||||
| 		{[]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"}}, | ||||
| 		{[]string{"http://localhost:9000/d1", "http://localhost:9001/d2", "http://localhost:9002/d3", "http://localhost:9003/d4"}, []string{"localhost:9001", "localhost:9002", "localhost:9003"}}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, testCase := range testCases { | ||||
| 		endpoints, _ := NewEndpointList(testCase.endpointArgs...) | ||||
| 		remotePeers := GetRemotePeers(endpoints) | ||||
| 		if !reflect.DeepEqual(remotePeers, testCase.expectedResult) { | ||||
| 			t.Fatalf("expected: %v, got: %v", testCase.expectedResult, remotePeers) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -194,11 +194,7 @@ func TestErasureReadUtils(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	objLayer, _, err := initObjectLayer(endpoints) | ||||
| 	objLayer, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		removeRoots(disks) | ||||
| 		t.Fatal(err) | ||||
|  | ||||
| @ -94,6 +94,21 @@ type eventData struct { | ||||
| // New notification event constructs a new notification event message from | ||||
| // input request metadata which completed successfully. | ||||
| func newNotificationEvent(event eventData) NotificationEvent { | ||||
| 	getResponseOriginEndpointKey := func() string { | ||||
| 		host := globalMinioHost | ||||
| 		if host == "" { | ||||
| 			// FIXME: Send FQDN or hostname of this machine than sending IP address. | ||||
| 			host = localIP4.ToSlice()[0] | ||||
| 		} | ||||
| 
 | ||||
| 		scheme := httpScheme | ||||
| 		if globalIsSSL { | ||||
| 			scheme = httpsScheme | ||||
| 		} | ||||
| 
 | ||||
| 		return fmt.Sprintf("%s://%s:%s", scheme, host, globalMinioPort) | ||||
| 	} | ||||
| 
 | ||||
| 	// Fetch the region. | ||||
| 	region := serverConfig.GetRegion() | ||||
| 
 | ||||
| @ -103,14 +118,6 @@ func newNotificationEvent(event eventData) NotificationEvent { | ||||
| 	// Time when Minio finished processing the request. | ||||
| 	eventTime := UTCNow() | ||||
| 
 | ||||
| 	// API endpoint is captured here to be returned back | ||||
| 	// to the client for it to differentiate from which | ||||
| 	// server the request came from. | ||||
| 	var apiEndpoint string | ||||
| 	if len(globalAPIEndpoints) >= 1 { | ||||
| 		apiEndpoint = globalAPIEndpoints[0] | ||||
| 	} | ||||
| 
 | ||||
| 	// Fetch a hexadecimal representation of event time in nano seconds. | ||||
| 	uniqueID := mustGetRequestID(eventTime) | ||||
| 
 | ||||
| @ -131,7 +138,7 @@ func newNotificationEvent(event eventData) NotificationEvent { | ||||
| 			responseRequestIDKey: uniqueID, | ||||
| 			// Following is a custom response element to indicate | ||||
| 			// event origin server endpoint. | ||||
| 			responseOriginEndpointKey: apiEndpoint, | ||||
| 			responseOriginEndpointKey: getResponseOriginEndpointKey(), | ||||
| 		}, | ||||
| 		S3: eventMeta{ | ||||
| 			SchemaVersion:   eventSchemaVersion, | ||||
|  | ||||
| @ -19,7 +19,6 @@ package cmd | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| @ -40,11 +39,7 @@ func TestInitEventNotifierFaultyDisks(t *testing.T) { | ||||
| 		t.Fatal("Unable to create directories for FS backend. ", err) | ||||
| 	} | ||||
| 	defer removeRoots(disks) | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to initialize FS backend.", err) | ||||
| 	} | ||||
| @ -97,11 +92,7 @@ func TestInitEventNotifierWithPostgreSQL(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to create directories for FS backend. ", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	fs, _, err := initObjectLayer(endpoints) | ||||
| 	fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to initialize FS backend.", err) | ||||
| 	} | ||||
| @ -128,11 +119,7 @@ func TestInitEventNotifierWithNATS(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to create directories for FS backend. ", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	fs, _, err := initObjectLayer(endpoints) | ||||
| 	fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to initialize FS backend.", err) | ||||
| 	} | ||||
| @ -159,11 +146,7 @@ func TestInitEventNotifierWithWebHook(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to create directories for FS backend. ", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	fs, _, err := initObjectLayer(endpoints) | ||||
| 	fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to initialize FS backend.", err) | ||||
| 	} | ||||
| @ -190,11 +173,7 @@ func TestInitEventNotifierWithAMQP(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to create directories for FS backend. ", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	fs, _, err := initObjectLayer(endpoints) | ||||
| 	fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to initialize FS backend.", err) | ||||
| 	} | ||||
| @ -221,11 +200,7 @@ func TestInitEventNotifierWithElasticSearch(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to create directories for FS backend. ", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	fs, _, err := initObjectLayer(endpoints) | ||||
| 	fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to initialize FS backend.", err) | ||||
| 	} | ||||
| @ -252,11 +227,7 @@ func TestInitEventNotifierWithRedis(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to create directories for FS backend. ", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	fs, _, err := initObjectLayer(endpoints) | ||||
| 	fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unable to initialize FS backend.", err) | ||||
| 	} | ||||
| @ -276,15 +247,10 @@ func (s *TestPeerRPCServerData) Setup(t *testing.T) { | ||||
| 	s.testServer = StartTestPeersRPCServer(t, s.serverType) | ||||
| 
 | ||||
| 	// setup port and minio addr | ||||
| 	host, port, err := net.SplitHostPort(s.testServer.Server.Listener.Addr().String()) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Initialisation error: %v", err) | ||||
| 	} | ||||
| 	host, port := mustSplitHostPort(s.testServer.Server.Listener.Addr().String()) | ||||
| 	globalMinioHost = host | ||||
| 	globalMinioPort = port | ||||
| 	globalMinioAddr = getLocalAddress( | ||||
| 		s.testServer.SrvCmdCfg, | ||||
| 	) | ||||
| 	globalMinioAddr = getEndpointsLocalAddr(s.testServer.endpoints) | ||||
| 
 | ||||
| 	// initialize the peer client(s) | ||||
| 	initGlobalS3Peers(s.testServer.Disks) | ||||
|  | ||||
| @ -273,12 +273,8 @@ func TestFormatXLHealFreshDisks(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	// Create an instance of xl backend. | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| @ -309,12 +305,8 @@ func TestFormatXLHealFreshDisksErrorExpected(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	// Create an instance of xl backend. | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| @ -596,12 +588,8 @@ func TestInitFormatXLErrors(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	// Create an instance of xl backend. | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -702,12 +690,8 @@ func TestLoadFormatXLErrs(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	// Create an instance of xl backend. | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -733,11 +717,7 @@ func TestLoadFormatXLErrs(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -761,11 +741,7 @@ func TestLoadFormatXLErrs(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -787,11 +763,7 @@ func TestLoadFormatXLErrs(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -820,13 +792,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Everything is fine, should return nil | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -842,13 +809,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Disks 0..15 are nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -866,13 +828,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk returns Faulty Disk | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -892,13 +849,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk is not found, heal corrupted disks should return nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -914,13 +866,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Remove format.json of all disks | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -940,13 +887,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Corrupted format json in one disk | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -976,13 +918,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Everything is fine, should return nil | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -997,13 +934,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Disks 0..15 are nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -1021,13 +953,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk returns Faulty Disk | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -1047,13 +974,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk is not found, heal corrupted disks should return nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -1069,13 +991,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Remove format.json of all disks | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| @ -187,9 +187,6 @@ func gatewayMain(ctx *cli.Context) { | ||||
| 		fatalIf(aerr, "Failed to start minio server") | ||||
| 	}() | ||||
| 
 | ||||
| 	apiEndPoints, err := finalizeAPIEndpoints(apiServer.Addr) | ||||
| 	fatalIf(err, "Unable to finalize API endpoints for %s", apiServer.Addr) | ||||
| 
 | ||||
| 	// Once endpoints are finalized, initialize the new object api. | ||||
| 	globalObjLayerMutex.Lock() | ||||
| 	globalObjectAPI = newObject | ||||
| @ -202,7 +199,8 @@ func gatewayMain(ctx *cli.Context) { | ||||
| 			mode = globalMinioModeGatewayAzure | ||||
| 		} | ||||
| 		checkUpdate(mode) | ||||
| 		printGatewayStartupMessage(apiEndPoints, accessKey, secretKey, backendType) | ||||
| 		apiEndpoints := getAPIEndpoints(apiServer.Addr) | ||||
| 		printGatewayStartupMessage(apiEndpoints, accessKey, secretKey, backendType) | ||||
| 	} | ||||
| 
 | ||||
| 	<-globalServiceDoneCh | ||||
|  | ||||
| @ -18,7 +18,6 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/x509" | ||||
| 	"net/url" | ||||
| 	"runtime" | ||||
| 	"time" | ||||
| 
 | ||||
| @ -85,9 +84,6 @@ var ( | ||||
| 	// Holds the host that was passed using --address | ||||
| 	globalMinioHost = "" | ||||
| 
 | ||||
| 	// Holds the list of API endpoints for a given server. | ||||
| 	globalAPIEndpoints = []string{} | ||||
| 
 | ||||
| 	// Peer communication struct | ||||
| 	globalS3Peers = s3Peers{} | ||||
| 
 | ||||
| @ -103,8 +99,7 @@ var ( | ||||
| 	// Minio server user agent string. | ||||
| 	globalServerUserAgent = "Minio/" + ReleaseTag + " (" + runtime.GOOS + "; " + runtime.GOARCH + ")" | ||||
| 
 | ||||
| 	// url.URL endpoints of disks that belong to the object storage. | ||||
| 	globalEndpoints = []*url.URL{} | ||||
| 	globalEndpoints EndpointList | ||||
| 
 | ||||
| 	// Global server's network statistics | ||||
| 	globalConnStats = newConnStats() | ||||
|  | ||||
| @ -1,69 +0,0 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2016 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 ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"sort" | ||||
| ) | ||||
| 
 | ||||
| // byLastOctetValue implements sort.Interface used in sorting a list | ||||
| // of ip address by their last octet value. | ||||
| type byLastOctetValue []net.IP | ||||
| 
 | ||||
| func (n byLastOctetValue) Len() int      { return len(n) } | ||||
| func (n byLastOctetValue) Swap(i, j int) { n[i], n[j] = n[j], n[i] } | ||||
| func (n byLastOctetValue) Less(i, j int) bool { | ||||
| 	return []byte(n[i].To4())[3] < []byte(n[j].To4())[3] | ||||
| } | ||||
| 
 | ||||
| // getInterfaceIPv4s is synonymous to net.InterfaceAddrs() | ||||
| // returns net.IP IPv4 only representation of the net.Addr. | ||||
| // Additionally the returned list is sorted by their last | ||||
| // octet value. | ||||
| // | ||||
| // [The logic to sort by last octet is implemented to | ||||
| // prefer CIDRs with higher octects, this in-turn skips the | ||||
| // localhost/loopback address to be not preferred as the | ||||
| // first ip on the list. Subsequently this list helps us print | ||||
| // a user friendly message with appropriate values]. | ||||
| func getInterfaceIPv4s() ([]net.IP, error) { | ||||
| 	addrs, err := net.InterfaceAddrs() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("Unable to determine network interface address. %s", err) | ||||
| 	} | ||||
| 	// Go through each return network address and collate IPv4 addresses. | ||||
| 	var nips []net.IP | ||||
| 	for _, addr := range addrs { | ||||
| 		if addr.Network() == "ip+net" { | ||||
| 			var nip net.IP | ||||
| 			// Attempt to parse the addr through CIDR. | ||||
| 			nip, _, err = net.ParseCIDR(addr.String()) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("Unable to parse addrss %s, error %s", addr, err) | ||||
| 			} | ||||
| 			// Collect only IPv4 addrs. | ||||
| 			if nip.To4() != nil { | ||||
| 				nips = append(nips, nip) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// Sort the list of IPs by their last octet value. | ||||
| 	sort.Sort(sort.Reverse(byLastOctetValue(nips))) | ||||
| 	return nips, nil | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2016 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 "testing" | ||||
| 
 | ||||
| func TestGetInterfaceIPv4s(t *testing.T) { | ||||
| 	ipv4s, err := getInterfaceIPv4s() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %s", err) | ||||
| 	} | ||||
| 	for _, ip := range ipv4s { | ||||
| 		if ip.To4() == nil { | ||||
| 			t.Fatalf("Unexpected expecting only IPv4 addresses only %s", ip) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -90,9 +90,9 @@ func startLockMaintainence(lockServers []*lockServer) { | ||||
| } | ||||
| 
 | ||||
| // Register distributed NS lock handlers. | ||||
| func registerDistNSLockRouter(mux *router.Router, serverConfig serverCmdConfig) error { | ||||
| func registerDistNSLockRouter(mux *router.Router, endpoints EndpointList) error { | ||||
| 	// Initialize a new set of lock servers. | ||||
| 	lockServers := newLockServers(serverConfig) | ||||
| 	lockServers := newLockServers(endpoints) | ||||
| 
 | ||||
| 	// Start lock maintenance from all lock servers. | ||||
| 	startLockMaintainence(lockServers) | ||||
| @ -102,19 +102,18 @@ func registerDistNSLockRouter(mux *router.Router, serverConfig serverCmdConfig) | ||||
| } | ||||
| 
 | ||||
| // Create one lock server for every local storage rpc server. | ||||
| func newLockServers(srvConfig serverCmdConfig) (lockServers []*lockServer) { | ||||
| 	for _, ep := range srvConfig.endpoints { | ||||
| func newLockServers(endpoints EndpointList) (lockServers []*lockServer) { | ||||
| 	for _, endpoint := range endpoints { | ||||
| 		// Initialize new lock server for each local node. | ||||
| 		if isLocalStorage(ep) { | ||||
| 			// Create handler for lock RPCs | ||||
| 			locker := &lockServer{ | ||||
| 				serviceEndpoint: getPath(ep), | ||||
| 		if endpoint.IsLocal { | ||||
| 			lockServers = append(lockServers, &lockServer{ | ||||
| 				serviceEndpoint: endpoint.Path, | ||||
| 				mutex:           sync.Mutex{}, | ||||
| 				lockMap:         make(map[string][]lockRequesterInfo), | ||||
| 			} | ||||
| 			lockServers = append(lockServers, locker) | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return lockServers | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -17,7 +17,6 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"net/url" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| @ -433,66 +432,46 @@ func TestLockServers(t *testing.T) { | ||||
| 		globalIsDistXL = currentIsDistXL | ||||
| 	}() | ||||
| 
 | ||||
| 	case1Endpoints := mustGetNewEndpointList( | ||||
| 		"http://localhost:9000/mnt/disk1", | ||||
| 		"http://1.1.1.2:9000/mnt/disk2", | ||||
| 		"http://1.1.2.1:9000/mnt/disk3", | ||||
| 		"http://1.1.2.2:9000/mnt/disk4", | ||||
| 	) | ||||
| 	for i := range case1Endpoints { | ||||
| 		if case1Endpoints[i].Host == "localhost:9000" { | ||||
| 			case1Endpoints[i].IsLocal = true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	case2Endpoints := mustGetNewEndpointList( | ||||
| 		"http://localhost:9000/mnt/disk1", | ||||
| 		"http://localhost:9000/mnt/disk2", | ||||
| 		"http://1.1.2.1:9000/mnt/disk3", | ||||
| 		"http://1.1.2.2:9000/mnt/disk4", | ||||
| 	) | ||||
| 	for i := range case2Endpoints { | ||||
| 		if case2Endpoints[i].Host == "localhost:9000" { | ||||
| 			case2Endpoints[i].IsLocal = true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	globalMinioHost = "" | ||||
| 	testCases := []struct { | ||||
| 		isDistXL         bool | ||||
| 		srvCmdConfig     serverCmdConfig | ||||
| 		endpoints        EndpointList | ||||
| 		totalLockServers int | ||||
| 	}{ | ||||
| 		// Test - 1 one lock server initialized. | ||||
| 		{ | ||||
| 			isDistXL: true, | ||||
| 			srvCmdConfig: serverCmdConfig{ | ||||
| 				endpoints: []*url.URL{{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "localhost:9000", | ||||
| 					Path:   "/mnt/disk1", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.1.2:9000", | ||||
| 					Path:   "/mnt/disk2", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.2.1:9000", | ||||
| 					Path:   "/mnt/disk3", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.2.2:9000", | ||||
| 					Path:   "/mnt/disk4", | ||||
| 				}}, | ||||
| 			}, | ||||
| 			totalLockServers: 1, | ||||
| 		}, | ||||
| 		{true, case1Endpoints, 1}, | ||||
| 		// Test - 2 two servers possible. | ||||
| 		{ | ||||
| 			isDistXL: true, | ||||
| 			srvCmdConfig: serverCmdConfig{ | ||||
| 				endpoints: []*url.URL{{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "localhost:9000", | ||||
| 					Path:   "/mnt/disk1", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "localhost:9000", | ||||
| 					Path:   "/mnt/disk2", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.2.1:9000", | ||||
| 					Path:   "/mnt/disk3", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.2.2:9000", | ||||
| 					Path:   "/mnt/disk4", | ||||
| 				}}, | ||||
| 			}, | ||||
| 			totalLockServers: 2, | ||||
| 		}, | ||||
| 		{true, case2Endpoints, 2}, | ||||
| 	} | ||||
| 
 | ||||
| 	// Validates lock server initialization. | ||||
| 	for i, testCase := range testCases { | ||||
| 		globalIsDistXL = testCase.isDistXL | ||||
| 		lockServers := newLockServers(testCase.srvCmdConfig) | ||||
| 		lockServers := newLockServers(testCase.endpoints) | ||||
| 		if len(lockServers) != testCase.totalLockServers { | ||||
| 			t.Fatalf("Test %d: Expected total %d, got %d", i+1, testCase.totalLockServers, len(lockServers)) | ||||
| 		} | ||||
|  | ||||
| @ -42,19 +42,16 @@ func initDsyncNodes() error { | ||||
| 	// Initialize rpc lock client information only if this instance is a distributed setup. | ||||
| 	clnts := make([]dsync.NetLocker, len(globalEndpoints)) | ||||
| 	myNode := -1 | ||||
| 	for index, ep := range globalEndpoints { | ||||
| 		if ep == nil { | ||||
| 			return errInvalidArgument | ||||
| 		} | ||||
| 	for index, endpoint := range globalEndpoints { | ||||
| 		clnts[index] = newLockRPCClient(authConfig{ | ||||
| 			accessKey:       cred.AccessKey, | ||||
| 			secretKey:       cred.SecretKey, | ||||
| 			serverAddr:      ep.Host, | ||||
| 			serverAddr:      endpoint.Host, | ||||
| 			secureConn:      globalIsSSL, | ||||
| 			serviceEndpoint: pathutil.Join(minioReservedBucketPath, lockServicePath, getPath(ep)), | ||||
| 			serviceEndpoint: pathutil.Join(minioReservedBucketPath, lockServicePath, endpoint.Path), | ||||
| 			serviceName:     lockServiceName, | ||||
| 		}) | ||||
| 		if isLocalStorage(ep) && myNode == -1 { | ||||
| 		if endpoint.IsLocal && myNode == -1 { | ||||
| 			myNode = index | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										167
									
								
								cmd/net.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								cmd/net.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,167 @@ | ||||
| /* | ||||
|  * 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 ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/set" | ||||
| ) | ||||
| 
 | ||||
| // IPv4 addresses of local host. | ||||
| var localIP4 = mustGetLocalIP4() | ||||
| 
 | ||||
| // mustSplitHostPort is a wrapper to net.SplitHostPort() where error is assumed to be a fatal. | ||||
| func mustSplitHostPort(hostPort string) (host, port string) { | ||||
| 	host, port, err := net.SplitHostPort(hostPort) | ||||
| 	fatalIf(err, "Unable to split host port %s", hostPort) | ||||
| 	return host, port | ||||
| } | ||||
| 
 | ||||
| // mustGetLocalIP4 returns IPv4 addresses of local host.  It panics on error. | ||||
| func mustGetLocalIP4() (ipList set.StringSet) { | ||||
| 	ipList = set.NewStringSet() | ||||
| 	addrs, err := net.InterfaceAddrs() | ||||
| 	fatalIf(err, "Unable to get IP addresses of this host.") | ||||
| 
 | ||||
| 	for _, addr := range addrs { | ||||
| 		var ip net.IP | ||||
| 		switch v := addr.(type) { | ||||
| 		case *net.IPNet: | ||||
| 			ip = v.IP | ||||
| 		case *net.IPAddr: | ||||
| 			ip = v.IP | ||||
| 		} | ||||
| 
 | ||||
| 		if ip.To4() != nil { | ||||
| 			ipList.Add(ip.String()) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ipList | ||||
| } | ||||
| 
 | ||||
| // getHostIP4 returns IPv4 address of given host. | ||||
| func getHostIP4(host string) (ipList set.StringSet, err error) { | ||||
| 	ipList = set.NewStringSet() | ||||
| 	ips, err := net.LookupIP(host) | ||||
| 	if err != nil { | ||||
| 		return ipList, err | ||||
| 	} | ||||
| 
 | ||||
| 	for _, ip := range ips { | ||||
| 		if ip.To4() != nil { | ||||
| 			ipList.Add(ip.String()) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ipList, err | ||||
| } | ||||
| 
 | ||||
| func getAPIEndpoints(serverAddr string) (apiEndpoints []string) { | ||||
| 	host, port := mustSplitHostPort(serverAddr) | ||||
| 
 | ||||
| 	var ipList []string | ||||
| 	if host == "" { | ||||
| 		ipList = localIP4.ToSlice() | ||||
| 	} else { | ||||
| 		ipList = []string{host} | ||||
| 	} | ||||
| 
 | ||||
| 	sort.Strings(ipList) | ||||
| 
 | ||||
| 	scheme := httpScheme | ||||
| 	if globalIsSSL { | ||||
| 		scheme = httpsScheme | ||||
| 	} | ||||
| 
 | ||||
| 	for _, ip := range ipList { | ||||
| 		apiEndpoints = append(apiEndpoints, fmt.Sprintf("%s://%s:%s", scheme, ip, port)) | ||||
| 	} | ||||
| 
 | ||||
| 	return apiEndpoints | ||||
| } | ||||
| 
 | ||||
| // checkPortAvailability - check if given port is already in use. | ||||
| // Note: The check method tries to listen on given port and closes it. | ||||
| // It is possible to have a disconnected client in this tiny window of time. | ||||
| func checkPortAvailability(port string) (err error) { | ||||
| 	// Return true if err is "address already in use" error. | ||||
| 	isAddrInUseErr := func(err error) (b bool) { | ||||
| 		if opErr, ok := err.(*net.OpError); ok { | ||||
| 			if sysErr, ok := opErr.Err.(*os.SyscallError); ok { | ||||
| 				if errno, ok := sysErr.Err.(syscall.Errno); ok { | ||||
| 					b = (errno == syscall.EADDRINUSE) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return b | ||||
| 	} | ||||
| 
 | ||||
| 	network := []string{"tcp", "tcp4", "tcp6"} | ||||
| 	for _, n := range network { | ||||
| 		l, err := net.Listen(n, net.JoinHostPort("", port)) | ||||
| 		if err == nil { | ||||
| 			// As we are able to listen on this network, the port is not in use. | ||||
| 			// Close the listener and continue check other networks. | ||||
| 			if err = l.Close(); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} else if isAddrInUseErr(err) { | ||||
| 			// As we got EADDRINUSE error, the port is in use by other process. | ||||
| 			// Return the error. | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // CheckLocalServerAddr - checks if serverAddr is valid and local host. | ||||
| func CheckLocalServerAddr(serverAddr string) error { | ||||
| 	host, port, err := net.SplitHostPort(serverAddr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Check whether port is a valid port number. | ||||
| 	p, err := strconv.Atoi(port) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("invalid port number") | ||||
| 	} else if p < 1 || p > 65535 { | ||||
| 		return fmt.Errorf("port number must be between 1 to 65535") | ||||
| 	} | ||||
| 
 | ||||
| 	if host != "" { | ||||
| 		hostIPs, err := getHostIP4(host) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		if localIP4.Intersection(hostIPs).IsEmpty() { | ||||
| 			return fmt.Errorf("host in server address should be this server") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										183
									
								
								cmd/net_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								cmd/net_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,183 @@ | ||||
| /* | ||||
|  * 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 ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"runtime" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/set" | ||||
| ) | ||||
| 
 | ||||
| func TestMustSplitHostPort(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		hostPort     string | ||||
| 		expectedHost string | ||||
| 		expectedPort string | ||||
| 	}{ | ||||
| 		{":54321", "", "54321"}, | ||||
| 		{"server:54321", "server", "54321"}, | ||||
| 		{":", "", ""}, | ||||
| 		{":0", "", "0"}, | ||||
| 		{":-10", "", "-10"}, | ||||
| 		{"server:100000000", "server", "100000000"}, | ||||
| 		{"server:https", "server", "https"}, | ||||
| 	} | ||||
| 
 | ||||
| 	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 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) { | ||||
| 	_, err := getHostIP4("myserver") | ||||
| 	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}, | ||||
| 		{"myserver", nil, err}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, testCase := range testCases { | ||||
| 		ipList, err := getHostIP4(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) { | ||||
| 	testCases := []struct { | ||||
| 		serverAddr     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 { | ||||
| 		apiEndpoints := getAPIEndpoints(testCase.serverAddr) | ||||
| 		apiEndpointSet := set.CreateStringSet(apiEndpoints...) | ||||
| 		if !apiEndpointSet.Contains(testCase.expectedResult) { | ||||
| 			t.Fatalf("test %d: expected: Found, got: Not Found", i+1) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 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 { | ||||
| 		port        string | ||||
| 		expectedErr error | ||||
| 	}{ | ||||
| 		{port, fmt.Errorf("listen tcp :%v: bind: address already in use", port)}, | ||||
| 		{getFreePort(), nil}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, testCase := range testCases { | ||||
| 		// On MS Windows, skip checking error case due to https://github.com/golang/go/issues/7598 | ||||
| 		if runtime.GOOS == globalWindowsOSName && testCase.expectedErr != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		err := checkPortAvailability(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}, | ||||
| 		{"", fmt.Errorf("missing port in address")}, | ||||
| 		{"localhost", fmt.Errorf("missing port in address localhost")}, | ||||
| 		{"example.org:54321", fmt.Errorf("host in server address should be this server")}, | ||||
| 		{":0", fmt.Errorf("port number must be between 1 to 65535")}, | ||||
| 		{":-10", fmt.Errorf("port number must be between 1 to 65535")}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, testCase := range testCases { | ||||
| 		err := CheckLocalServerAddr(testCase.serverAddr) | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -17,9 +17,6 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	humanize "github.com/dustin/go-humanize" | ||||
| @ -132,90 +129,13 @@ func houseKeeping(storageDisks []StorageAPI) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Check if a network path is local to this node. | ||||
| func isLocalStorage(ep *url.URL) bool { | ||||
| 	if ep.Host == "" { | ||||
| 		return true | ||||
| 	} | ||||
| 	if globalMinioHost != "" && globalMinioPort != "" { | ||||
| 		// if --address host:port was specified for distXL we short | ||||
| 		// circuit only the endPoint that matches host:port | ||||
| 		return net.JoinHostPort(globalMinioHost, globalMinioPort) == ep.Host | ||||
| 	} | ||||
| 	// Split host to extract host information. | ||||
| 	host, _, err := net.SplitHostPort(ep.Host) | ||||
| 	if err != nil { | ||||
| 		errorIf(err, "Cannot split host port") | ||||
| 		return false | ||||
| 	} | ||||
| 	// Resolve host to address to check if the IP is loopback. | ||||
| 	// If address resolution fails, assume it's a non-local host. | ||||
| 	addrs, err := net.LookupHost(host) | ||||
| 	if err != nil { | ||||
| 		errorIf(err, "Failed to lookup host") | ||||
| 		return false | ||||
| 	} | ||||
| 	for _, addr := range addrs { | ||||
| 		if ip := net.ParseIP(addr); ip.IsLoopback() { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	iaddrs, err := net.InterfaceAddrs() | ||||
| 	if err != nil { | ||||
| 		errorIf(err, "Unable to list interface addresses") | ||||
| 		return false | ||||
| 	} | ||||
| 	for _, addr := range addrs { | ||||
| 		for _, iaddr := range iaddrs { | ||||
| 			ip, _, err := net.ParseCIDR(iaddr.String()) | ||||
| 			if err != nil { | ||||
| 				errorIf(err, "Unable to parse CIDR") | ||||
| 				return false | ||||
| 			} | ||||
| 			if ip.String() == addr { | ||||
| 				return true | ||||
| 			} | ||||
| 
 | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // Fetch the path component from *url.URL*. | ||||
| func getPath(ep *url.URL) string { | ||||
| 	if ep == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	var diskPath string | ||||
| 	// For windows ep.Path is usually empty | ||||
| 	if runtime.GOOS == globalWindowsOSName { | ||||
| 		switch ep.Scheme { | ||||
| 		case "": | ||||
| 			// Eg. "minio server .\export" | ||||
| 			diskPath = ep.Path | ||||
| 		case httpScheme, httpsScheme: | ||||
| 			// For full URLs windows drive is part of URL path. | ||||
| 			// Eg: http://ip:port/C:\mydrive | ||||
| 			// For windows trim off the preceding "/". | ||||
| 			diskPath = ep.Path[1:] | ||||
| 		default: | ||||
| 			// For the rest url splits drive letter into | ||||
| 			// Scheme contruct the disk path back. | ||||
| 			diskPath = ep.Scheme + ":" + ep.Opaque | ||||
| 		} | ||||
| 	} else { | ||||
| 		// For other operating systems ep.Path is non empty. | ||||
| 		diskPath = ep.Path | ||||
| 	} | ||||
| 	return diskPath | ||||
| } | ||||
| 
 | ||||
| // Depending on the disk type network or local, initialize storage API. | ||||
| func newStorageAPI(ep *url.URL) (storage StorageAPI, err error) { | ||||
| 	if isLocalStorage(ep) { | ||||
| 		return newPosix(getPath(ep)) | ||||
| func newStorageAPI(endpoint Endpoint) (storage StorageAPI, err error) { | ||||
| 	if endpoint.IsLocal { | ||||
| 		return newPosix(endpoint.Path) | ||||
| 	} | ||||
| 	return newStorageRPC(ep) | ||||
| 
 | ||||
| 	return newStorageRPC(endpoint), nil | ||||
| } | ||||
| 
 | ||||
| var initMetaVolIgnoredErrs = append(baseIgnoredErrs, errVolumeExists) | ||||
|  | ||||
| @ -17,7 +17,6 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| ) | ||||
| @ -98,53 +97,3 @@ func TestHouseKeeping(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Test getPath() - the path that needs to be passed to newPosix() | ||||
| func TestGetPath(t *testing.T) { | ||||
| 	globalMinioHost = "" | ||||
| 	var testCases []struct { | ||||
| 		epStr string | ||||
| 		path  string | ||||
| 	} | ||||
| 	if runtime.GOOS == globalWindowsOSName { | ||||
| 		testCases = []struct { | ||||
| 			epStr string | ||||
| 			path  string | ||||
| 		}{ | ||||
| 			{"\\export", "\\export"}, | ||||
| 			{"D:\\export", "d:\\export"}, | ||||
| 			{"D:\\", "d:\\"}, | ||||
| 			{"D:", "d:"}, | ||||
| 			{"\\", "\\"}, | ||||
| 			{"http://127.0.0.1/d:/export", "d:/export"}, | ||||
| 			{"https://127.0.0.1/d:/export", "d:/export"}, | ||||
| 		} | ||||
| 	} else { | ||||
| 		testCases = []struct { | ||||
| 			epStr string | ||||
| 			path  string | ||||
| 		}{ | ||||
| 			{"/export", "/export"}, | ||||
| 			{"http://127.0.0.1/export", "/export"}, | ||||
| 			{"https://127.0.0.1/export", "/export"}, | ||||
| 		} | ||||
| 	} | ||||
| 	testCasesCommon := []struct { | ||||
| 		epStr string | ||||
| 		path  string | ||||
| 	}{ | ||||
| 		{"export", "export"}, | ||||
| 	} | ||||
| 	testCases = append(testCases, testCasesCommon...) | ||||
| 	for i, test := range testCases { | ||||
| 		eps, err := parseStorageEndpoints([]string{test.epStr}) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Test %d: %s - %s", i+1, test.epStr, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		path := getPath(eps[0]) | ||||
| 		if path != test.path { | ||||
| 			t.Errorf("Test %d: For endpoing %s, getPath() failed, got: %s, expected: %s,", i+1, test.epStr, path, test.path) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -18,8 +18,6 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	humanize "github.com/dustin/go-humanize" | ||||
| @ -52,45 +50,11 @@ func printOnceFn() printOnceFunc { | ||||
| } | ||||
| 
 | ||||
| // Prints custom message when healing is required for XL and Distributed XL backend. | ||||
| func printHealMsg(endpoints []*url.URL, storageDisks []StorageAPI, fn printOnceFunc) { | ||||
| func printHealMsg(endpoints EndpointList, storageDisks []StorageAPI, fn printOnceFunc) { | ||||
| 	msg := getHealMsg(endpoints, storageDisks) | ||||
| 	fn(msg) | ||||
| } | ||||
| 
 | ||||
| // Heal endpoint constructs the final endpoint URL for control heal command. | ||||
| // Disk heal endpoint needs to be just a URL and no special paths. | ||||
| // This function constructs the right endpoint under various conditions | ||||
| // for single node XL, distributed XL and when minio server is bound | ||||
| // to a specific ip:port. | ||||
| func getHealEndpoint(tls bool, firstEndpoint *url.URL) (cEndpoint *url.URL) { | ||||
| 	scheme := httpScheme | ||||
| 	if tls { | ||||
| 		scheme = httpsScheme | ||||
| 	} | ||||
| 	cEndpoint = &url.URL{ | ||||
| 		Scheme: scheme, | ||||
| 	} | ||||
| 	// Bind to `--address host:port` was specified. | ||||
| 	if globalMinioHost != "" { | ||||
| 		cEndpoint.Host = net.JoinHostPort(globalMinioHost, globalMinioPort) | ||||
| 		return cEndpoint | ||||
| 	} | ||||
| 	// For distributed XL setup. | ||||
| 	if firstEndpoint.Host != "" { | ||||
| 		cEndpoint.Host = firstEndpoint.Host | ||||
| 		return cEndpoint | ||||
| 	} | ||||
| 	// For single node XL setup, we need to find the endpoint. | ||||
| 	cEndpoint.Host = globalMinioAddr | ||||
| 	// Fetch all the listening ips. For single node XL we | ||||
| 	// just use the first host. | ||||
| 	hosts, _, err := getListenIPs(cEndpoint.Host) | ||||
| 	if err == nil { | ||||
| 		cEndpoint.Host = net.JoinHostPort(hosts[0], globalMinioPort) | ||||
| 	} | ||||
| 	return cEndpoint | ||||
| } | ||||
| 
 | ||||
| // Disks offline and online strings.. | ||||
| const ( | ||||
| 	diskOffline = "offline" | ||||
| @ -100,7 +64,7 @@ const ( | ||||
| // Constructs a formatted heal message, when cluster is found to be in state where it requires healing. | ||||
| // healing is optional, server continues to initialize object layer after printing this message. | ||||
| // it is upto the end user to perform a heal if needed. | ||||
| func getHealMsg(endpoints []*url.URL, storageDisks []StorageAPI) string { | ||||
| func getHealMsg(endpoints EndpointList, storageDisks []StorageAPI) string { | ||||
| 	healFmtCmd := `"mc admin heal myminio"` | ||||
| 	msg := fmt.Sprintf("New disk(s) were found, format them by running - %s\n", | ||||
| 		healFmtCmd) | ||||
| @ -126,13 +90,13 @@ func getHealMsg(endpoints []*url.URL, storageDisks []StorageAPI) string { | ||||
| } | ||||
| 
 | ||||
| // Prints regular message when we have sufficient disks to start the cluster. | ||||
| func printRegularMsg(endpoints []*url.URL, storageDisks []StorageAPI, fn printOnceFunc) { | ||||
| func printRegularMsg(endpoints EndpointList, storageDisks []StorageAPI, fn printOnceFunc) { | ||||
| 	msg := getStorageInitMsg("\nInitializing data volume.", endpoints, storageDisks) | ||||
| 	fn(msg) | ||||
| } | ||||
| 
 | ||||
| // Constructs a formatted regular message when we have sufficient disks to start the cluster. | ||||
| func getStorageInitMsg(titleMsg string, endpoints []*url.URL, storageDisks []StorageAPI) string { | ||||
| func getStorageInitMsg(titleMsg string, endpoints EndpointList, storageDisks []StorageAPI) string { | ||||
| 	msg := colorBlue(titleMsg) | ||||
| 	disksInfo, _, _ := getDisksInfo(storageDisks) | ||||
| 	for i, info := range disksInfo { | ||||
| @ -156,7 +120,7 @@ func getStorageInitMsg(titleMsg string, endpoints []*url.URL, storageDisks []Sto | ||||
| } | ||||
| 
 | ||||
| // Prints initialization message when cluster is being initialized for the first time. | ||||
| func printFormatMsg(endpoints []*url.URL, storageDisks []StorageAPI, fn printOnceFunc) { | ||||
| func printFormatMsg(endpoints EndpointList, storageDisks []StorageAPI, fn printOnceFunc) { | ||||
| 	msg := getStorageInitMsg("\nInitializing data volume for the first time.", endpoints, storageDisks) | ||||
| 	fn(msg) | ||||
| } | ||||
|  | ||||
| @ -17,60 +17,10 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"net/url" | ||||
| 	"reflect" | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| // Tests and validates the output for heal endpoint. | ||||
| func TestGetHealEndpoint(t *testing.T) { | ||||
| 	// Test for a SSL scheme. | ||||
| 	tls := true | ||||
| 	hURL := getHealEndpoint(tls, &url.URL{ | ||||
| 		Scheme: httpScheme, | ||||
| 		Host:   "localhost:9000", | ||||
| 	}) | ||||
| 	sHURL := &url.URL{ | ||||
| 		Scheme: httpsScheme, | ||||
| 		Host:   "localhost:9000", | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(hURL, sHURL) { | ||||
| 		t.Fatalf("Expected %#v, but got %#v", sHURL, hURL) | ||||
| 	} | ||||
| 
 | ||||
| 	// Test a non-TLS scheme. | ||||
| 	tls = false | ||||
| 	hURL = getHealEndpoint(tls, &url.URL{ | ||||
| 		Scheme: httpsScheme, | ||||
| 		Host:   "localhost:9000", | ||||
| 	}) | ||||
| 	sHURL = &url.URL{ | ||||
| 		Scheme: httpScheme, | ||||
| 		Host:   "localhost:9000", | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(hURL, sHURL) { | ||||
| 		t.Fatalf("Expected %#v, but got %#v", sHURL, hURL) | ||||
| 	} | ||||
| 
 | ||||
| 	// FIXME(GLOBAL): purposefully Host is left empty because | ||||
| 	// we need to bring in safe handling on global values | ||||
| 	// add a proper test case here once that happens. | ||||
| 	/* | ||||
| 		tls = false | ||||
| 		hURL = getHealEndpoint(tls, &url.URL{ | ||||
| 			Path: "/export", | ||||
| 		}) | ||||
| 		sHURL = &url.URL{ | ||||
| 			Scheme: httpScheme, | ||||
| 			Host:   "", | ||||
| 		} | ||||
| 		globalMinioAddr = "" | ||||
| 		if !reflect.DeepEqual(hURL, sHURL) { | ||||
| 			t.Fatalf("Expected %#v, but got %#v", sHURL, hURL) | ||||
| 		} | ||||
| 	*/ | ||||
| } | ||||
| 
 | ||||
| // Tests heal message to be correct and properly formatted. | ||||
| func TestHealMsg(t *testing.T) { | ||||
| 	rootPath, err := newTestConfig(globalMinioDefaultRegion) | ||||
| @ -85,39 +35,26 @@ func TestHealMsg(t *testing.T) { | ||||
| 	nilDisks[5] = nil | ||||
| 	authErrs := make([]error, len(storageDisks)) | ||||
| 	authErrs[5] = errAuthentication | ||||
| 	endpointURL, err := url.Parse("http://10.1.10.1:9000") | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected error:", err) | ||||
| 	} | ||||
| 	endpointURLs := make([]*url.URL, len(storageDisks)) | ||||
| 	for idx := 0; idx < len(endpointURLs); idx++ { | ||||
| 		endpointURLs[idx] = endpointURL | ||||
| 
 | ||||
| 	args := []string{} | ||||
| 	for i := range storageDisks { | ||||
| 		args = append(args, fmt.Sprintf("http://10.1.10.%d:9000/d1", i+1)) | ||||
| 	} | ||||
| 	endpoints := mustGetNewEndpointList(args...) | ||||
| 
 | ||||
| 	testCases := []struct { | ||||
| 		endPoints    []*url.URL | ||||
| 		endPoints    EndpointList | ||||
| 		storageDisks []StorageAPI | ||||
| 		serrs        []error | ||||
| 	}{ | ||||
| 		// Test - 1 for valid disks and errors. | ||||
| 		{ | ||||
| 			endPoints:    endpointURLs, | ||||
| 			storageDisks: storageDisks, | ||||
| 			serrs:        errs, | ||||
| 		}, | ||||
| 		{endpoints, storageDisks, errs}, | ||||
| 		// Test - 2 for one of the disks is nil. | ||||
| 		{ | ||||
| 			endPoints:    endpointURLs, | ||||
| 			storageDisks: nilDisks, | ||||
| 			serrs:        errs, | ||||
| 		}, | ||||
| 		{endpoints, nilDisks, errs}, | ||||
| 		// Test - 3 for one of the errs is authentication. | ||||
| 		{ | ||||
| 			endPoints:    endpointURLs, | ||||
| 			storageDisks: nilDisks, | ||||
| 			serrs:        authErrs, | ||||
| 		}, | ||||
| 		{endpoints, nilDisks, authErrs}, | ||||
| 	} | ||||
| 
 | ||||
| 	for i, testCase := range testCases { | ||||
| 		msg := getHealMsg(testCase.endPoints, testCase.storageDisks) | ||||
| 		if msg == "" { | ||||
|  | ||||
| @ -18,7 +18,6 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/minio/mc/pkg/console" | ||||
| @ -191,7 +190,7 @@ func printRetryMsg(sErrs []error, storageDisks []StorageAPI) { | ||||
| 
 | ||||
| // Implements a jitter backoff loop for formatting all disks during | ||||
| // initialization of the server. | ||||
| func retryFormattingXLDisks(firstDisk bool, endpoints []*url.URL, storageDisks []StorageAPI) error { | ||||
| func retryFormattingXLDisks(firstDisk bool, endpoints EndpointList, storageDisks []StorageAPI) error { | ||||
| 	if len(endpoints) == 0 { | ||||
| 		return errInvalidArgument | ||||
| 	} | ||||
| @ -276,16 +275,13 @@ func retryFormattingXLDisks(firstDisk bool, endpoints []*url.URL, storageDisks [ | ||||
| } | ||||
| 
 | ||||
| // Initialize storage disks based on input arguments. | ||||
| func initStorageDisks(endpoints []*url.URL) ([]StorageAPI, error) { | ||||
| func initStorageDisks(endpoints EndpointList) ([]StorageAPI, error) { | ||||
| 	// Bootstrap disks. | ||||
| 	storageDisks := make([]StorageAPI, len(endpoints)) | ||||
| 	for index, ep := range endpoints { | ||||
| 		if ep == nil { | ||||
| 			return nil, errInvalidArgument | ||||
| 		} | ||||
| 	for index, endpoint := range endpoints { | ||||
| 		// Intentionally ignore disk not found errors. XL is designed | ||||
| 		// to handle these errors internally. | ||||
| 		storage, err := newStorageAPI(ep) | ||||
| 		storage, err := newStorageAPI(endpoint) | ||||
| 		if err != nil && err != errDiskNotFound { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @ -295,14 +291,10 @@ func initStorageDisks(endpoints []*url.URL) ([]StorageAPI, error) { | ||||
| } | ||||
| 
 | ||||
| // Format disks before initialization of object layer. | ||||
| func waitForFormatXLDisks(firstDisk bool, endpoints []*url.URL, storageDisks []StorageAPI) (formattedDisks []StorageAPI, err error) { | ||||
| func waitForFormatXLDisks(firstDisk bool, endpoints EndpointList, storageDisks []StorageAPI) (formattedDisks []StorageAPI, err error) { | ||||
| 	if len(endpoints) == 0 { | ||||
| 		return nil, errInvalidArgument | ||||
| 	} | ||||
| 	firstEndpoint := endpoints[0] | ||||
| 	if firstEndpoint == nil { | ||||
| 		return nil, errInvalidArgument | ||||
| 	} | ||||
| 	if storageDisks == nil { | ||||
| 		return nil, errInvalidArgument | ||||
| 	} | ||||
|  | ||||
| @ -30,15 +30,15 @@ func newObjectLayerFn() (layer ObjectLayer) { | ||||
| } | ||||
| 
 | ||||
| // Composed function registering routers for only distributed XL setup. | ||||
| func registerDistXLRouters(mux *router.Router, srvCmdConfig serverCmdConfig) error { | ||||
| func registerDistXLRouters(mux *router.Router, endpoints EndpointList) error { | ||||
| 	// Register storage rpc router only if its a distributed setup. | ||||
| 	err := registerStorageRPCRouters(mux, srvCmdConfig) | ||||
| 	err := registerStorageRPCRouters(mux, endpoints) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Register distributed namespace lock. | ||||
| 	err = registerDistNSLockRouter(mux, srvCmdConfig) | ||||
| 	err = registerDistNSLockRouter(mux, endpoints) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -54,14 +54,14 @@ func registerDistXLRouters(mux *router.Router, srvCmdConfig serverCmdConfig) err | ||||
| } | ||||
| 
 | ||||
| // configureServer handler returns final handler for the http server. | ||||
| func configureServerHandler(srvCmdConfig serverCmdConfig) (http.Handler, error) { | ||||
| func configureServerHandler(endpoints EndpointList) (http.Handler, error) { | ||||
| 	// Initialize router. `SkipClean(true)` stops gorilla/mux from | ||||
| 	// normalizing URL path minio/minio#3256 | ||||
| 	mux := router.NewRouter().SkipClean(true) | ||||
| 
 | ||||
| 	// Initialize distributed NS lock. | ||||
| 	if globalIsDistXL { | ||||
| 		registerDistXLRouters(mux, srvCmdConfig) | ||||
| 		registerDistXLRouters(mux, endpoints) | ||||
| 	} | ||||
| 
 | ||||
| 	// Add Admin RPC router | ||||
|  | ||||
| @ -19,9 +19,11 @@ package cmd | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"net" | ||||
| 	"path" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/set" | ||||
| ) | ||||
| 
 | ||||
| // s3Peer structs contains the address of a peer in the cluster, and | ||||
| @ -39,53 +41,44 @@ type s3Peers []s3Peer | ||||
| // makeS3Peers makes an s3Peers struct value from the given urls | ||||
| // slice. The urls slice is assumed to be non-empty and free of nil | ||||
| // values. | ||||
| func makeS3Peers(eps []*url.URL) s3Peers { | ||||
| 	var ret []s3Peer | ||||
| 
 | ||||
| 	// map to store peers that are already added to ret | ||||
| 	seenAddr := make(map[string]bool) | ||||
| 
 | ||||
| 	// add local (self) as peer in the array | ||||
| 	ret = append(ret, s3Peer{ | ||||
| 		globalMinioAddr, | ||||
| func makeS3Peers(endpoints EndpointList) (s3PeerList s3Peers) { | ||||
| 	thisPeer := globalMinioAddr | ||||
| 	if globalMinioHost == "" { | ||||
| 		thisPeer = net.JoinHostPort("localhost", globalMinioPort) | ||||
| 	} | ||||
| 	s3PeerList = append(s3PeerList, s3Peer{ | ||||
| 		thisPeer, | ||||
| 		&localBucketMetaState{ObjectAPI: newObjectLayerFn}, | ||||
| 	}) | ||||
| 	seenAddr[globalMinioAddr] = true | ||||
| 
 | ||||
| 	serverCred := serverConfig.GetCredential() | ||||
| 	// iterate over endpoints to find new remote peers and add | ||||
| 	// them to ret. | ||||
| 	for _, ep := range eps { | ||||
| 		if ep.Host == "" { | ||||
| 	hostSet := set.CreateStringSet(globalMinioAddr) | ||||
| 	cred := serverConfig.GetCredential() | ||||
| 	serviceEndpoint := path.Join(minioReservedBucketPath, s3Path) | ||||
| 	for _, host := range GetRemotePeers(endpoints) { | ||||
| 		if hostSet.Contains(host) { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// Check if the remote host has been added already | ||||
| 		if !seenAddr[ep.Host] { | ||||
| 			cfg := authConfig{ | ||||
| 				accessKey:       serverCred.AccessKey, | ||||
| 				secretKey:       serverCred.SecretKey, | ||||
| 				serverAddr:      ep.Host, | ||||
| 				serviceEndpoint: path.Join(minioReservedBucketPath, s3Path), | ||||
| 		hostSet.Add(host) | ||||
| 		s3PeerList = append(s3PeerList, s3Peer{ | ||||
| 			addr: host, | ||||
| 			bmsClient: &remoteBucketMetaState{newAuthRPCClient(authConfig{ | ||||
| 				accessKey:       cred.AccessKey, | ||||
| 				secretKey:       cred.SecretKey, | ||||
| 				serverAddr:      host, | ||||
| 				serviceEndpoint: serviceEndpoint, | ||||
| 				secureConn:      globalIsSSL, | ||||
| 				serviceName:     "S3", | ||||
| 			} | ||||
| 
 | ||||
| 			ret = append(ret, s3Peer{ | ||||
| 				addr:      ep.Host, | ||||
| 				bmsClient: &remoteBucketMetaState{newAuthRPCClient(cfg)}, | ||||
| 			})}, | ||||
| 		}) | ||||
| 			seenAddr[ep.Host] = true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ret | ||||
| 	return s3PeerList | ||||
| } | ||||
| 
 | ||||
| // initGlobalS3Peers - initialize globalS3Peers by passing in | ||||
| // endpoints - intended to be called early in program start-up. | ||||
| func initGlobalS3Peers(eps []*url.URL) { | ||||
| 	globalS3Peers = makeS3Peers(eps) | ||||
| func initGlobalS3Peers(endpoints EndpointList) { | ||||
| 	globalS3Peers = makeS3Peers(endpoints) | ||||
| } | ||||
| 
 | ||||
| // GetPeerClient - fetch BucketMetaState interface by peer address | ||||
|  | ||||
| @ -17,7 +17,6 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"net/url" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
| @ -35,12 +34,12 @@ func TestMakeS3Peers(t *testing.T) { | ||||
| 	// test cases | ||||
| 	testCases := []struct { | ||||
| 		gMinioAddr string | ||||
| 		eps        []*url.URL | ||||
| 		eps        EndpointList | ||||
| 		peers      []string | ||||
| 	}{ | ||||
| 		{":9000", []*url.URL{{Path: "/mnt/disk1"}}, []string{":9000"}}, | ||||
| 		{":9000", []*url.URL{{Host: "localhost:9001"}}, []string{":9000", "localhost:9001"}}, | ||||
| 		{"m1:9000", []*url.URL{{Host: "m1:9000"}, {Host: "m2:9000"}, {Host: "m3:9000"}}, []string{"m1:9000", "m2:9000", "m3:9000"}}, | ||||
| 		{"127.0.0.1:9000", mustGetNewEndpointList("/mnt/disk1"), []string{"127.0.0.1:9000"}}, | ||||
| 		{"127.0.0.1:9000", mustGetNewEndpointList("http://localhost:9001/d1"), []string{"127.0.0.1:9000", "localhost:9001"}}, | ||||
| 		{"example.org:9000", mustGetNewEndpointList("http://example.org:9000/d1", "http://example.com:9000/d1", "http://example.net:9000/d1", "http://example.edu:9000/d1"), []string{"example.org:9000", "example.com:9000", "example.edu:9000", "example.net:9000"}}, | ||||
| 	} | ||||
| 
 | ||||
| 	getPeersHelper := func(s3p s3Peers) []string { | ||||
|  | ||||
| @ -18,19 +18,12 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"runtime" | ||||
| 
 | ||||
| 	"github.com/minio/cli" | ||||
| ) | ||||
| 
 | ||||
| @ -110,249 +103,6 @@ func enableLoggers() { | ||||
| 	log.SetConsoleTarget(consoleLogTarget) | ||||
| } | ||||
| 
 | ||||
| type serverCmdConfig struct { | ||||
| 	serverAddr string | ||||
| 	endpoints  []*url.URL | ||||
| } | ||||
| 
 | ||||
| // Parse an array of end-points (from the command line) | ||||
| func parseStorageEndpoints(eps []string) (endpoints []*url.URL, err error) { | ||||
| 	for _, ep := range eps { | ||||
| 		if ep == "" { | ||||
| 			return nil, errInvalidArgument | ||||
| 		} | ||||
| 		var u *url.URL | ||||
| 		u, err = url.Parse(ep) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if u.Host != "" { | ||||
| 			_, port, err := net.SplitHostPort(u.Host) | ||||
| 			// Ignore the missing port error as the default port can be globalMinioPort. | ||||
| 			if err != nil && !strings.Contains(err.Error(), "missing port in address") { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			if globalMinioHost == "" { | ||||
| 				// For ex.: minio server host1:port1 host2:port2... | ||||
| 				// we return error as port is configurable only | ||||
| 				// using "--address :port" | ||||
| 				if port != "" { | ||||
| 					return nil, fmt.Errorf("Invalid Argument %s, port configurable using --address :<port>", u.Host) | ||||
| 				} | ||||
| 				u.Host = net.JoinHostPort(u.Host, globalMinioPort) | ||||
| 			} else { | ||||
| 				// For ex.: minio server --address host:port host1:port1 host2:port2... | ||||
| 				// i.e if "--address host:port" is specified | ||||
| 				// port info in u.Host is mandatory else return error. | ||||
| 				if port == "" { | ||||
| 					return nil, fmt.Errorf("Invalid Argument %s, port mandatory when --address <host>:<port> is used", u.Host) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		endpoints = append(endpoints, u) | ||||
| 	} | ||||
| 	return endpoints, nil | ||||
| } | ||||
| 
 | ||||
| // Validate if input disks are sufficient for initializing XL. | ||||
| func checkSufficientDisks(eps []*url.URL) error { | ||||
| 	// Verify total number of disks. | ||||
| 	total := len(eps) | ||||
| 	if total > maxErasureBlocks { | ||||
| 		return errXLMaxDisks | ||||
| 	} | ||||
| 	if total < minErasureBlocks { | ||||
| 		return errXLMinDisks | ||||
| 	} | ||||
| 
 | ||||
| 	// isEven function to verify if a given number if even. | ||||
| 	isEven := func(number int) bool { | ||||
| 		return number%2 == 0 | ||||
| 	} | ||||
| 
 | ||||
| 	// Verify if we have even number of disks. | ||||
| 	// only combination of 4, 6, 8, 10, 12, 14, 16 are supported. | ||||
| 	if !isEven(total) { | ||||
| 		return errXLNumDisks | ||||
| 	} | ||||
| 
 | ||||
| 	// Success. | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Returns if slice of disks is a distributed setup. | ||||
| func isDistributedSetup(eps []*url.URL) bool { | ||||
| 	// Validate if one the disks is not local. | ||||
| 	for _, ep := range eps { | ||||
| 		if !isLocalStorage(ep) { | ||||
| 			// One or more disks supplied as arguments are | ||||
| 			// not attached to the local node. | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // Returns true if path is empty, or equals to '.', '/', '\' characters. | ||||
| func isPathSentinel(path string) bool { | ||||
| 	return path == "" || path == "." || path == "/" || path == `\` | ||||
| } | ||||
| 
 | ||||
| // Returned when path is empty or root path. | ||||
| var errEmptyRootPath = errors.New("Empty or root path is not allowed") | ||||
| 
 | ||||
| // Invalid scheme passed. | ||||
| var errInvalidScheme = errors.New("Invalid scheme") | ||||
| 
 | ||||
| // Check if endpoint is in expected syntax by valid scheme/path across all platforms. | ||||
| func checkEndpointURL(endpointURL *url.URL) (err error) { | ||||
| 	// Applicable to all OS. | ||||
| 	if endpointURL.Scheme == "" || endpointURL.Scheme == httpScheme || endpointURL.Scheme == httpsScheme { | ||||
| 		if isPathSentinel(path.Clean(endpointURL.Path)) { | ||||
| 			err = errEmptyRootPath | ||||
| 		} | ||||
| 
 | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Applicable to Windows only. | ||||
| 	if runtime.GOOS == globalWindowsOSName { | ||||
| 		// On Windows, endpoint can be a path with drive eg. C:\Export and its URL.Scheme is 'C'. | ||||
| 		// Check if URL.Scheme is a single letter alphabet to represent a drive. | ||||
| 		// Note: URL.Parse() converts scheme into lower case always. | ||||
| 		if len(endpointURL.Scheme) == 1 && endpointURL.Scheme[0] >= 'a' && endpointURL.Scheme[0] <= 'z' { | ||||
| 			// If endpoint is C:\ or C:\export, URL.Path does not have path information like \ or \export | ||||
| 			// hence we directly work with endpoint. | ||||
| 			if isPathSentinel(strings.SplitN(path.Clean(endpointURL.String()), ":", 2)[1]) { | ||||
| 				err = errEmptyRootPath | ||||
| 			} | ||||
| 
 | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return errInvalidScheme | ||||
| } | ||||
| 
 | ||||
| // Check if endpoints are in expected syntax by valid scheme/path across all platforms. | ||||
| func checkEndpointsSyntax(eps []*url.URL, disks []string) error { | ||||
| 	for i, u := range eps { | ||||
| 		if err := checkEndpointURL(u); err != nil { | ||||
| 			return fmt.Errorf("%s: %s (%s)", err.Error(), u.Path, disks[i]) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Make sure all the command line parameters are OK and exit in case of invalid parameters. | ||||
| func checkServerSyntax(endpoints []*url.URL, disks []string) { | ||||
| 	// Validate if endpoints follow the expected syntax. | ||||
| 	err := checkEndpointsSyntax(endpoints, disks) | ||||
| 	fatalIf(err, "Invalid endpoints found %s", strings.Join(disks, " ")) | ||||
| 
 | ||||
| 	// Validate for duplicate endpoints are supplied. | ||||
| 	err = checkDuplicateEndpoints(endpoints) | ||||
| 	fatalIf(err, "Duplicate entries in %s", strings.Join(disks, " ")) | ||||
| 
 | ||||
| 	if len(endpoints) > 1 { | ||||
| 		// Validate if we have sufficient disks for XL setup. | ||||
| 		err = checkSufficientDisks(endpoints) | ||||
| 		fatalIf(err, "Insufficient number of disks.") | ||||
| 	} else { | ||||
| 		// Validate if we have invalid disk for FS setup. | ||||
| 		if endpoints[0].Host != "" && endpoints[0].Scheme != "" { | ||||
| 			fatalIf(errInvalidArgument, "%s, FS setup expects a filesystem path", endpoints[0]) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if !isDistributedSetup(endpoints) { | ||||
| 		// for FS and singlenode-XL validation is done, return. | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Rest of the checks applies only to distributed XL setup. | ||||
| 	if globalMinioHost != "" { | ||||
| 		// We are here implies --address host:port is passed, hence the user is trying | ||||
| 		// to run one minio process per export disk. | ||||
| 		if globalMinioPort == "" { | ||||
| 			fatalIf(errInvalidArgument, "Port missing, Host:Port should be specified for --address") | ||||
| 		} | ||||
| 		foundCnt := 0 | ||||
| 		for _, ep := range endpoints { | ||||
| 			if ep.Host == globalMinioAddr { | ||||
| 				foundCnt++ | ||||
| 			} | ||||
| 		} | ||||
| 		if foundCnt == 0 { | ||||
| 			// --address host:port should be available in the XL disk list. | ||||
| 			fatalIf(errInvalidArgument, "%s is not available in %s", globalMinioAddr, strings.Join(disks, " ")) | ||||
| 		} | ||||
| 		if foundCnt > 1 { | ||||
| 			// --address host:port should match exactly one entry in the XL disk list. | ||||
| 			fatalIf(errInvalidArgument, "%s matches % entries in %s", globalMinioAddr, foundCnt, strings.Join(disks, " ")) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, ep := range endpoints { | ||||
| 		if ep.Scheme == httpsScheme && !globalIsSSL { | ||||
| 			// Certificates should be provided for https configuration. | ||||
| 			fatalIf(errInvalidArgument, "Certificates not provided for secure configuration") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Checks if any of the endpoints supplied is local to this server. | ||||
| func isAnyEndpointLocal(eps []*url.URL) bool { | ||||
| 	anyLocalEp := false | ||||
| 	for _, ep := range eps { | ||||
| 		if isLocalStorage(ep) { | ||||
| 			anyLocalEp = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return anyLocalEp | ||||
| } | ||||
| 
 | ||||
| // Returned when there are no ports. | ||||
| var errEmptyPort = errors.New("Port cannot be empty or '0', please use `--address` to pick a specific port") | ||||
| 
 | ||||
| // Convert an input address of form host:port into, host and port, returns if any. | ||||
| func getHostPort(address string) (host, port string, err error) { | ||||
| 	// Check if requested port is available. | ||||
| 	host, port, err = net.SplitHostPort(address) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
| 
 | ||||
| 	// Empty ports. | ||||
| 	if port == "0" || port == "" { | ||||
| 		// Port zero or empty means use requested to choose any freely available | ||||
| 		// port. Avoid this since it won't work with any configured clients, | ||||
| 		// can lead to serious loss of availability. | ||||
| 		return "", "", errEmptyPort | ||||
| 	} | ||||
| 
 | ||||
| 	// Parse port. | ||||
| 	if _, err = strconv.Atoi(port); err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
| 
 | ||||
| 	if runtime.GOOS == "darwin" { | ||||
| 		// On macOS, if a process already listens on 127.0.0.1:PORT, net.Listen() falls back | ||||
| 		// to IPv6 address ie minio will start listening on IPv6 address whereas another | ||||
| 		// (non-)minio process is listening on IPv4 of given port. | ||||
| 		// To avoid this error sutiation we check for port availability only for macOS. | ||||
| 		if err = checkPortAvailability(port); err != nil { | ||||
| 			return "", "", err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Success. | ||||
| 	return host, port, nil | ||||
| } | ||||
| 
 | ||||
| func initConfig() { | ||||
| 	// Config file does not exist, we create it fresh and return upon success. | ||||
| 	if isFile(getConfigFile()) { | ||||
| @ -365,6 +115,8 @@ func initConfig() { | ||||
| } | ||||
| 
 | ||||
| func serverHandleCmdArgs(ctx *cli.Context) { | ||||
| 	// Set configuration directory. | ||||
| 	{ | ||||
| 		// Get configuration directory from command line argument. | ||||
| 		configDir := ctx.String("config-dir") | ||||
| 		if !ctx.IsSet("config-dir") && ctx.GlobalIsSet("config-dir") { | ||||
| @ -375,52 +127,34 @@ func serverHandleCmdArgs(ctx *cli.Context) { | ||||
| 		} | ||||
| 
 | ||||
| 		// Disallow relative paths, figure out absolute paths. | ||||
| 	{ | ||||
| 		configDirAbs, err := filepath.Abs(configDir) | ||||
| 		fatalIf(err, "Unable to fetch absolute path for config directory %s", configDir) | ||||
| 
 | ||||
| 		configDir = configDirAbs | ||||
| 		setConfigDir(configDirAbs) | ||||
| 	} | ||||
| 
 | ||||
| 	// Set configuration directory. | ||||
| 	setConfigDir(configDir) | ||||
| 
 | ||||
| 	// Server address. | ||||
| 	globalMinioAddr = ctx.String("address") | ||||
| 	serverAddr := ctx.String("address") | ||||
| 	fatalIf(CheckLocalServerAddr(serverAddr), "Invalid address ‘%s’ in command line argument.", serverAddr) | ||||
| 
 | ||||
| 	var setupType SetupType | ||||
| 	var err error | ||||
| 	globalMinioHost, globalMinioPort, err = getHostPort(globalMinioAddr) | ||||
| 	fatalIf(err, "Unable to extract host and port %s", globalMinioAddr) | ||||
| 
 | ||||
| 	// Disks to be used in server init. | ||||
| 	endpoints, err := parseStorageEndpoints(ctx.Args()) | ||||
| 	fatalIf(err, "Unable to parse storage endpoints %s", ctx.Args()) | ||||
| 
 | ||||
| 	// Sort endpoints for consistent ordering across multiple | ||||
| 	// nodes in a distributed setup. This is to avoid format.json | ||||
| 	// corruption if the disks aren't supplied in the same order | ||||
| 	// on all nodes. | ||||
| 	sort.Sort(byHostPath(endpoints)) | ||||
| 
 | ||||
| 	checkServerSyntax(endpoints, ctx.Args()) | ||||
| 
 | ||||
| 	// Should exit gracefully if none of the endpoints passed | ||||
| 	// as command line args are local to this server. | ||||
| 	if !isAnyEndpointLocal(endpoints) { | ||||
| 		fatalIf(errInvalidArgument, "None of the disks passed as command line args are local to this server.") | ||||
| 	globalMinioAddr, globalEndpoints, setupType, err = CreateEndpoints(serverAddr, ctx.Args()...) | ||||
| 	fatalIf(err, "Invalid command line arguments server=‘%s’, args=%s", serverAddr, ctx.Args()) | ||||
| 	globalMinioHost, globalMinioPort = mustSplitHostPort(globalMinioAddr) | ||||
| 	if runtime.GOOS == "darwin" { | ||||
| 		// On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back | ||||
| 		// to IPv6 address ie minio will start listening on IPv6 address whereas another | ||||
| 		// (non-)minio process is listening on IPv4 of given port. | ||||
| 		// To avoid this error sutiation we check for port availability only for macOS. | ||||
| 		fatalIf(checkPortAvailability(globalMinioPort), "Port %d already in use", globalMinioPort) | ||||
| 	} | ||||
| 
 | ||||
| 	// Check if endpoints are part of distributed setup. | ||||
| 	globalIsDistXL = isDistributedSetup(endpoints) | ||||
| 
 | ||||
| 	// Set globalIsXL if erasure code backend is about to be | ||||
| 	// initialized for the given endpoints. | ||||
| 	if len(endpoints) > 1 { | ||||
| 	globalIsXL = (setupType == XLSetupType) | ||||
| 	globalIsDistXL = (setupType == DistXLSetupType) | ||||
| 	if globalIsDistXL { | ||||
| 		globalIsXL = true | ||||
| 	} | ||||
| 
 | ||||
| 	// Set endpoints of []*url.URL type to globalEndpoints. | ||||
| 	globalEndpoints = endpoints | ||||
| } | ||||
| 
 | ||||
| func serverHandleEnvVars() { | ||||
| @ -497,11 +231,10 @@ func serverMain(ctx *cli.Context) { | ||||
| 	if !quietFlag { | ||||
| 		// Check for new updates from dl.minio.io. | ||||
| 		mode := globalMinioModeFS | ||||
| 		if globalIsXL { | ||||
| 			mode = globalMinioModeXL | ||||
| 		} | ||||
| 		if globalIsDistXL { | ||||
| 			mode = globalMinioModeDistXL | ||||
| 		} else if globalIsXL { | ||||
| 			mode = globalMinioModeXL | ||||
| 		} | ||||
| 		checkUpdate(mode) | ||||
| 	} | ||||
| @ -518,31 +251,18 @@ func serverMain(ctx *cli.Context) { | ||||
| 	initNSLock(globalIsDistXL) | ||||
| 
 | ||||
| 	// Configure server. | ||||
| 	srvConfig := serverCmdConfig{ | ||||
| 		serverAddr: globalMinioAddr, | ||||
| 		endpoints:  globalEndpoints, | ||||
| 	} | ||||
| 
 | ||||
| 	// Configure server. | ||||
| 	handler, err := configureServerHandler(srvConfig) | ||||
| 	handler, err := configureServerHandler(globalEndpoints) | ||||
| 	fatalIf(err, "Unable to configure one of server's RPC services.") | ||||
| 
 | ||||
| 	// Initialize a new HTTP server. | ||||
| 	apiServer := NewServerMux(globalMinioAddr, handler) | ||||
| 
 | ||||
| 	// Set the global minio addr for this server. | ||||
| 	globalMinioAddr = getLocalAddress(srvConfig) | ||||
| 
 | ||||
| 	// Initialize S3 Peers inter-node communication only in distributed setup. | ||||
| 	initGlobalS3Peers(globalEndpoints) | ||||
| 
 | ||||
| 	// Initialize Admin Peers inter-node communication only in distributed setup. | ||||
| 	initGlobalAdminPeers(globalEndpoints) | ||||
| 
 | ||||
| 	// Determine API endpoints where we are going to serve the S3 API from. | ||||
| 	globalAPIEndpoints, err = finalizeAPIEndpoints(apiServer.Addr) | ||||
| 	fatalIf(err, "Unable to finalize API endpoints for %s", apiServer.Addr) | ||||
| 
 | ||||
| 	// Start server, automatically configures TLS if certs are available. | ||||
| 	go func() { | ||||
| 		cert, key := "", "" | ||||
| @ -552,7 +272,7 @@ func serverMain(ctx *cli.Context) { | ||||
| 		fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server.") | ||||
| 	}() | ||||
| 
 | ||||
| 	newObject, err := newObjectLayer(srvConfig) | ||||
| 	newObject, err := newObjectLayer(globalEndpoints) | ||||
| 	fatalIf(err, "Initializing object layer failed") | ||||
| 
 | ||||
| 	globalObjLayerMutex.Lock() | ||||
| @ -560,7 +280,8 @@ func serverMain(ctx *cli.Context) { | ||||
| 	globalObjLayerMutex.Unlock() | ||||
| 
 | ||||
| 	// Prints the formatted startup message once object layer is initialized. | ||||
| 	printStartupMessage(globalAPIEndpoints) | ||||
| 	apiEndpoints := getAPIEndpoints(apiServer.Addr) | ||||
| 	printStartupMessage(apiEndpoints) | ||||
| 
 | ||||
| 	// Set uptime time after object layer has initialized. | ||||
| 	globalBootTime = UTCNow() | ||||
| @ -570,40 +291,26 @@ func serverMain(ctx *cli.Context) { | ||||
| } | ||||
| 
 | ||||
| // Initialize object layer with the supplied disks, objectLayer is nil upon any error. | ||||
| func newObjectLayer(srvCmdCfg serverCmdConfig) (newObject ObjectLayer, err error) { | ||||
| func newObjectLayer(endpoints EndpointList) (newObject ObjectLayer, err error) { | ||||
| 	// For FS only, directly use the disk. | ||||
| 	isFS := len(srvCmdCfg.endpoints) == 1 | ||||
| 	isFS := len(endpoints) == 1 | ||||
| 	if isFS { | ||||
| 		// Unescape is needed for some UNC paths on windows | ||||
| 		// which are of this form \\127.0.0.1\\export\test. | ||||
| 		var fsPath string | ||||
| 		fsPath, err = url.QueryUnescape(srvCmdCfg.endpoints[0].String()) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		// Initialize new FS object layer. | ||||
| 		newObject, err = newFSObjectLayer(fsPath) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		return newFSObjectLayer(endpoints[0].Path) | ||||
| 	} | ||||
| 
 | ||||
| 		// FS initialized, return. | ||||
| 		return newObject, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// First disk argument check if it is local. | ||||
| 	firstDisk := isLocalStorage(srvCmdCfg.endpoints[0]) | ||||
| 
 | ||||
| 	// Initialize storage disks. | ||||
| 	storageDisks, err := initStorageDisks(srvCmdCfg.endpoints) | ||||
| 	storageDisks, err := initStorageDisks(endpoints) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Wait for formatting disks for XL backend. | ||||
| 	var formattedDisks []StorageAPI | ||||
| 	formattedDisks, err = waitForFormatXLDisks(firstDisk, srvCmdCfg.endpoints, storageDisks) | ||||
| 
 | ||||
| 	// First disk argument check if it is local. | ||||
| 	firstDisk := endpoints[0].IsLocal | ||||
| 	formattedDisks, err = waitForFormatXLDisks(firstDisk, endpoints, storageDisks) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @ -17,205 +17,10 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"reflect" | ||||
| 	"runtime" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestGetListenIPs(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		addr       string | ||||
| 		port       string | ||||
| 		shouldPass bool | ||||
| 	}{ | ||||
| 		{"127.0.0.1", "9000", true}, | ||||
| 		{"", "9000", true}, | ||||
| 		{"", "", false}, | ||||
| 	} | ||||
| 	for _, test := range testCases { | ||||
| 		var addr string | ||||
| 		// Please keep this we need to do this because | ||||
| 		// of odd https://play.golang.org/p/4dMPtM6Wdd | ||||
| 		// implementation issue. | ||||
| 		if test.port != "" { | ||||
| 			addr = test.addr + ":" + test.port | ||||
| 		} | ||||
| 		hosts, port, err := getListenIPs(addr) | ||||
| 		if !test.shouldPass && err == nil { | ||||
| 			t.Fatalf("Test should fail but succeeded %s", err) | ||||
| 		} | ||||
| 		if test.shouldPass && err != nil { | ||||
| 			t.Fatalf("Test should succeed but failed %s", err) | ||||
| 		} | ||||
| 		if test.shouldPass { | ||||
| 			if port != test.port { | ||||
| 				t.Errorf("Test expected %s, got %s", test.port, port) | ||||
| 			} | ||||
| 			if len(hosts) == 0 { | ||||
| 				t.Errorf("Test unexpected value hosts cannot be empty %#v", test) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests get host port. | ||||
| func TestGetHostPort(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		addr string | ||||
| 		err  error | ||||
| 	}{ | ||||
| 		// Test 1 - successful. | ||||
| 		{ | ||||
| 			addr: ":" + getFreePort(), | ||||
| 			err:  nil, | ||||
| 		}, | ||||
| 		// Test 2 port empty. | ||||
| 		{ | ||||
| 			addr: ":0", | ||||
| 			err:  errEmptyPort, | ||||
| 		}, | ||||
| 		// Test 3 port empty. | ||||
| 		{ | ||||
| 			addr: ":", | ||||
| 			err:  errEmptyPort, | ||||
| 		}, | ||||
| 		// Test 4 invalid port. | ||||
| 		{ | ||||
| 			addr: "linux:linux", | ||||
| 			err:  errors.New("strconv.ParseInt: parsing \"linux\": invalid syntax"), | ||||
| 		}, | ||||
| 		// Test 5 port not present. | ||||
| 		{ | ||||
| 			addr: "hostname", | ||||
| 			err:  errors.New("missing port in address hostname"), | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	// Validate all tests. | ||||
| 	for i, testCase := range testCases { | ||||
| 		_, _, err := getHostPort(testCase.addr) | ||||
| 		if err != nil { | ||||
| 			if err.Error() != testCase.err.Error() { | ||||
| 				t.Fatalf("Test %d: Error: %s", i+1, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests finalize api endpoints. | ||||
| func TestFinalizeAPIEndpoints(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		addr string | ||||
| 	}{ | ||||
| 		{":80"}, | ||||
| 		{":80"}, | ||||
| 		{"127.0.0.1:80"}, | ||||
| 		{"127.0.0.1:80"}, | ||||
| 	} | ||||
| 
 | ||||
| 	for i, test := range testCases { | ||||
| 		endPoints, err := finalizeAPIEndpoints(test.addr) | ||||
| 		if err != nil && len(endPoints) <= 0 { | ||||
| 			t.Errorf("Test case %d returned with no API end points for %s", | ||||
| 				i+1, test.addr) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests all the expected input disks for function checkSufficientDisks. | ||||
| func TestCheckSufficientDisks(t *testing.T) { | ||||
| 	var xlDisks []string | ||||
| 	if runtime.GOOS == globalWindowsOSName { | ||||
| 		xlDisks = []string{ | ||||
| 			"C:\\mnt\\backend1", | ||||
| 			"C:\\mnt\\backend2", | ||||
| 			"C:\\mnt\\backend3", | ||||
| 			"C:\\mnt\\backend4", | ||||
| 			"C:\\mnt\\backend5", | ||||
| 			"C:\\mnt\\backend6", | ||||
| 			"C:\\mnt\\backend7", | ||||
| 			"C:\\mnt\\backend8", | ||||
| 			"C:\\mnt\\backend9", | ||||
| 			"C:\\mnt\\backend10", | ||||
| 			"C:\\mnt\\backend11", | ||||
| 			"C:\\mnt\\backend12", | ||||
| 			"C:\\mnt\\backend13", | ||||
| 			"C:\\mnt\\backend14", | ||||
| 			"C:\\mnt\\backend15", | ||||
| 			"C:\\mnt\\backend16", | ||||
| 			"C:\\mnt\\backend17", | ||||
| 		} | ||||
| 	} else { | ||||
| 		xlDisks = []string{ | ||||
| 			"/mnt/backend1", | ||||
| 			"/mnt/backend2", | ||||
| 			"/mnt/backend3", | ||||
| 			"/mnt/backend4", | ||||
| 			"/mnt/backend5", | ||||
| 			"/mnt/backend6", | ||||
| 			"/mnt/backend7", | ||||
| 			"/mnt/backend8", | ||||
| 			"/mnt/backend9", | ||||
| 			"/mnt/backend10", | ||||
| 			"/mnt/backend11", | ||||
| 			"/mnt/backend12", | ||||
| 			"/mnt/backend13", | ||||
| 			"/mnt/backend14", | ||||
| 			"/mnt/backend15", | ||||
| 			"/mnt/backend16", | ||||
| 			"/mnt/backend17", | ||||
| 		} | ||||
| 	} | ||||
| 	// List of test cases fo sufficient disk verification. | ||||
| 	testCases := []struct { | ||||
| 		disks       []string | ||||
| 		expectedErr error | ||||
| 	}{ | ||||
| 		// Even number of disks '6'. | ||||
| 		{ | ||||
| 			xlDisks[0:6], | ||||
| 			nil, | ||||
| 		}, | ||||
| 		// Even number of disks '12'. | ||||
| 		{ | ||||
| 			xlDisks[0:12], | ||||
| 			nil, | ||||
| 		}, | ||||
| 		// Even number of disks '16'. | ||||
| 		{ | ||||
| 			xlDisks[0:16], | ||||
| 			nil, | ||||
| 		}, | ||||
| 		// Larger than maximum number of disks > 16. | ||||
| 		{ | ||||
| 			xlDisks, | ||||
| 			errXLMaxDisks, | ||||
| 		}, | ||||
| 		// Lesser than minimum number of disks < 6. | ||||
| 		{ | ||||
| 			xlDisks[0:3], | ||||
| 			errXLMinDisks, | ||||
| 		}, | ||||
| 		// Odd number of disks, not divisible by '2'. | ||||
| 		{ | ||||
| 			append(xlDisks[0:10], xlDisks[11]), | ||||
| 			errXLNumDisks, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	// Validates different variations of input disks. | ||||
| 	for i, testCase := range testCases { | ||||
| 		endpoints, err := parseStorageEndpoints(testCase.disks) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Unexpected error %s", err) | ||||
| 		} | ||||
| 		if checkSufficientDisks(endpoints) != testCase.expectedErr { | ||||
| 			t.Errorf("Test %d expected to pass for disks %s", i+1, testCase.disks) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests initializing new object layer. | ||||
| func TestNewObjectLayer(t *testing.T) { | ||||
| 	// Tests for FS object layer. | ||||
| @ -226,15 +31,8 @@ func TestNewObjectLayer(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(disks) | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected parse error", err) | ||||
| 	} | ||||
| 
 | ||||
| 	obj, err := newObjectLayer(serverCmdConfig{ | ||||
| 		serverAddr: ":9000", | ||||
| 		endpoints:  endpoints, | ||||
| 	}) | ||||
| 	endpoints := mustGetNewEndpointList(disks...) | ||||
| 	obj, err := newObjectLayer(endpoints) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected object layer initialization error", err) | ||||
| 	} | ||||
| @ -253,15 +51,8 @@ func TestNewObjectLayer(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(disks) | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected parse error", err) | ||||
| 	} | ||||
| 
 | ||||
| 	obj, err = newObjectLayer(serverCmdConfig{ | ||||
| 		serverAddr: ":9000", | ||||
| 		endpoints:  endpoints, | ||||
| 	}) | ||||
| 	endpoints = mustGetNewEndpointList(disks...) | ||||
| 	obj, err = newObjectLayer(endpoints) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected object layer initialization error", err) | ||||
| 	} | ||||
| @ -271,176 +62,3 @@ func TestNewObjectLayer(t *testing.T) { | ||||
| 		t.Fatal("Unexpected object layer detected", reflect.TypeOf(obj)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests parsing various types of input endpoints and paths. | ||||
| func TestParseStorageEndpoints(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		globalMinioHost string | ||||
| 		host            string | ||||
| 		expectedErr     error | ||||
| 	}{ | ||||
| 		{"", "http://127.0.0.1/export", nil}, | ||||
| 		{ | ||||
| 			"testhost", | ||||
| 			"http://127.0.0.1/export", | ||||
| 			errors.New("Invalid Argument 127.0.0.1, port mandatory when --address <host>:<port> is used"), | ||||
| 		}, | ||||
| 		{ | ||||
| 			"", | ||||
| 			"http://127.0.0.1:9000/export", | ||||
| 			errors.New("Invalid Argument 127.0.0.1:9000, port configurable using --address :<port>"), | ||||
| 		}, | ||||
| 		{"testhost", "http://127.0.0.1:9000/export", nil}, | ||||
| 	} | ||||
| 	for i, test := range testCases { | ||||
| 		globalMinioHost = test.globalMinioHost | ||||
| 		_, err := parseStorageEndpoints([]string{test.host}) | ||||
| 		if err != nil { | ||||
| 			if err.Error() != test.expectedErr.Error() { | ||||
| 				t.Errorf("Test %d : got %v, expected %v", i+1, err, test.expectedErr) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// Should be reset back to "" so that we don't affect other tests. | ||||
| 	globalMinioHost = "" | ||||
| } | ||||
| 
 | ||||
| // Test check endpoints syntax function for syntax verification | ||||
| // across various scenarios of inputs. | ||||
| func TestCheckEndpointsSyntax(t *testing.T) { | ||||
| 	successCases := []string{ | ||||
| 		"export", | ||||
| 		"/export", | ||||
| 		"http://127.0.0.1/export", | ||||
| 		"https://127.0.0.1/export", | ||||
| 	} | ||||
| 
 | ||||
| 	failureCases := []string{ | ||||
| 		"/", | ||||
| 		"http://127.0.0.1", | ||||
| 		"http://127.0.0.1/", | ||||
| 		"ftp://127.0.0.1/export", | ||||
| 		"server:/export", | ||||
| 	} | ||||
| 
 | ||||
| 	if runtime.GOOS == globalWindowsOSName { | ||||
| 		successCases = append(successCases, | ||||
| 			`\export`, | ||||
| 			`D:\export`, | ||||
| 		) | ||||
| 
 | ||||
| 		failureCases = append(failureCases, | ||||
| 			"D:", | ||||
| 			`D:\`, | ||||
| 			`\`, | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, disk := range successCases { | ||||
| 		eps, err := parseStorageEndpoints([]string{disk}) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Unable to parse %s, error %s", disk, err) | ||||
| 		} | ||||
| 		if err = checkEndpointsSyntax(eps, []string{disk}); err != nil { | ||||
| 			t.Errorf("expected: <nil>, got: %s", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, disk := range failureCases { | ||||
| 		eps, err := parseStorageEndpoints([]string{disk}) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Unable to parse %s, error %s", disk, err) | ||||
| 		} | ||||
| 		if err = checkEndpointsSyntax(eps, []string{disk}); err == nil { | ||||
| 			t.Errorf("expected: <error>, got: <nil>") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestIsDistributedSetup(t *testing.T) { | ||||
| 	var testCases []struct { | ||||
| 		disks  []string | ||||
| 		result bool | ||||
| 	} | ||||
| 	if runtime.GOOS == globalWindowsOSName { | ||||
| 		testCases = []struct { | ||||
| 			disks  []string | ||||
| 			result bool | ||||
| 		}{ | ||||
| 			{[]string{`http://4.4.4.4/c:\mnt\disk1`, `http://4.4.4.4/c:\mnt\disk2`}, true}, | ||||
| 			{[]string{`http://4.4.4.4/c:\mnt\disk1`, `http://127.0.0.1/c:\mnt\disk2`}, true}, | ||||
| 			{[]string{`c:\mnt\disk1`, `c:\mnt\disk2`}, false}, | ||||
| 		} | ||||
| 	} else { | ||||
| 		testCases = []struct { | ||||
| 			disks  []string | ||||
| 			result bool | ||||
| 		}{ | ||||
| 			{[]string{"http://4.4.4.4/mnt/disk1", "http://4.4.4.4/mnt/disk2"}, true}, | ||||
| 			{[]string{"http://4.4.4.4/mnt/disk1", "http://127.0.0.1/mnt/disk2"}, true}, | ||||
| 			{[]string{"/mnt/disk1", "/mnt/disk2"}, false}, | ||||
| 		} | ||||
| 	} | ||||
| 	for i, test := range testCases { | ||||
| 		endpoints, err := parseStorageEndpoints(test.disks) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Test %d: Unexpected error: %s", i+1, err) | ||||
| 		} | ||||
| 		res := isDistributedSetup(endpoints) | ||||
| 		if res != test.result { | ||||
| 			t.Errorf("Test %d: expected result %t but received %t", i+1, test.result, res) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Test cases when globalMinioHost is set | ||||
| 	globalMinioHost = "testhost" | ||||
| 	testCases = []struct { | ||||
| 		disks  []string | ||||
| 		result bool | ||||
| 	}{ | ||||
| 		{[]string{"http://127.0.0.1:9001/mnt/disk1", "http://127.0.0.1:9002/mnt/disk2", "http://127.0.0.1:9003/mnt/disk3", "http://127.0.0.1:9004/mnt/disk4"}, true}, | ||||
| 		{[]string{"/mnt/disk1", "/mnt/disk2"}, false}, | ||||
| 	} | ||||
| 
 | ||||
| 	for i, test := range testCases { | ||||
| 		endpoints, err := parseStorageEndpoints(test.disks) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Test %d: Unexpected error: %s", i+1, err) | ||||
| 		} | ||||
| 		res := isDistributedSetup(endpoints) | ||||
| 		if res != test.result { | ||||
| 			t.Errorf("Test %d: expected result %t but received %t", i+1, test.result, res) | ||||
| 		} | ||||
| 	} | ||||
| 	// Reset so that we don't affect other tests. | ||||
| 	globalMinioHost = "" | ||||
| } | ||||
| 
 | ||||
| // Tests isAnyEndpointLocal function with inputs such that it returns true and false respectively. | ||||
| func TestIsAnyEndpointLocal(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		disks  []string | ||||
| 		result bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			disks: []string{"http://4.4.4.4/mnt/disk1", | ||||
| 				"http://4.4.4.4/mnt/disk1"}, | ||||
| 			result: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			disks: []string{"http://127.0.0.1/mnt/disk1", | ||||
| 				"http://127.0.0.1/mnt/disk1"}, | ||||
| 			result: true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for i, test := range testCases { | ||||
| 		endpoints, err := parseStorageEndpoints(test.disks) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Test %d - Failed to parse storage endpoints %v", i+1, err) | ||||
| 		} | ||||
| 		actual := isAnyEndpointLocal(endpoints) | ||||
| 		if actual != test.result { | ||||
| 			t.Errorf("Test %d - Expected %v but received %v", i+1, test.result, actual) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,71 +0,0 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2016, 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 ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| ) | ||||
| 
 | ||||
| // getListenIPs - gets all the ips to listen on. | ||||
| func getListenIPs(serverAddr string) (hosts []string, port string, err error) { | ||||
| 	var host string | ||||
| 	host, port, err = net.SplitHostPort(serverAddr) | ||||
| 	if err != nil { | ||||
| 		return nil, port, fmt.Errorf("Unable to parse host address %s", err) | ||||
| 	} | ||||
| 	if host == "" { | ||||
| 		var ipv4s []net.IP | ||||
| 		ipv4s, err = getInterfaceIPv4s() | ||||
| 		if err != nil { | ||||
| 			return nil, port, fmt.Errorf("Unable reverse sort ips from hosts %s", err) | ||||
| 		} | ||||
| 		for _, ip := range ipv4s { | ||||
| 			hosts = append(hosts, ip.String()) | ||||
| 		} | ||||
| 		return hosts, port, nil | ||||
| 	} // if host != "" { | ||||
| 
 | ||||
| 	// Proceed to append itself, since user requested a specific endpoint. | ||||
| 	hosts = append(hosts, host) | ||||
| 
 | ||||
| 	// Success. | ||||
| 	return hosts, port, nil | ||||
| } | ||||
| 
 | ||||
| // Finalizes the API endpoints based on the host list and port. | ||||
| func finalizeAPIEndpoints(addr string) (endPoints []string, err error) { | ||||
| 	// Verify current scheme. | ||||
| 	scheme := httpScheme | ||||
| 	if globalIsSSL { | ||||
| 		scheme = httpsScheme | ||||
| 	} | ||||
| 
 | ||||
| 	// Get list of listen ips and port. | ||||
| 	hosts, port, err1 := getListenIPs(addr) | ||||
| 	if err1 != nil { | ||||
| 		return nil, err1 | ||||
| 	} | ||||
| 
 | ||||
| 	// Construct proper endpoints. | ||||
| 	for _, host := range hosts { | ||||
| 		endPoints = append(endPoints, fmt.Sprintf("%s://%s:%s", scheme, host, port)) | ||||
| 	} | ||||
| 
 | ||||
| 	// Success. | ||||
| 	return endPoints, nil | ||||
| } | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2016 Minio, Inc. | ||||
|  * 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. | ||||
| @ -16,19 +16,29 @@ | ||||
| 
 | ||||
| package cmd | ||||
| 
 | ||||
| import "net/url" | ||||
| // SetupType - enum for setup type. | ||||
| type SetupType int | ||||
| 
 | ||||
| type byHostPath []*url.URL | ||||
| const ( | ||||
| 	// FSSetupType - FS setup type enum. | ||||
| 	FSSetupType SetupType = iota + 1 | ||||
| 
 | ||||
| func (s byHostPath) Swap(i, j int) { | ||||
| 	s[i], s[j] = s[j], s[i] | ||||
| 	// XLSetupType - XL setup type enum. | ||||
| 	XLSetupType | ||||
| 
 | ||||
| 	// DistXLSetupType - Distributed XL setup type enum. | ||||
| 	DistXLSetupType | ||||
| ) | ||||
| 
 | ||||
| func (setupType SetupType) String() string { | ||||
| 	switch setupType { | ||||
| 	case FSSetupType: | ||||
| 		return globalMinioModeFS | ||||
| 	case XLSetupType: | ||||
| 		return globalMinioModeXL | ||||
| 	case DistXLSetupType: | ||||
| 		return globalMinioModeDistXL | ||||
| 	} | ||||
| 
 | ||||
| func (s byHostPath) Len() int { | ||||
| 	return len(s) | ||||
| } | ||||
| 
 | ||||
| // Note: Host in url.URL includes the port too. | ||||
| func (s byHostPath) Less(i, j int) bool { | ||||
| 	return (s[i].Host + s[i].Path) < (s[j].Host + s[j].Path) | ||||
| 	return "" | ||||
| } | ||||
| @ -85,8 +85,12 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode { | ||||
| 	cred := serverConfig.GetCredential() | ||||
| 
 | ||||
| 	// r.RequestURI will have raw encoded URI as sent by the client. | ||||
| 	splits := splitStr(r.RequestURI, "?", 2) | ||||
| 	encodedResource, encodedQuery := splits[0], splits[1] | ||||
| 	tokens := strings.SplitN(r.RequestURI, "?", 2) | ||||
| 	encodedResource := tokens[0] | ||||
| 	encodedQuery := "" | ||||
| 	if len(tokens) == 2 { | ||||
| 		encodedQuery = tokens[1] | ||||
| 	} | ||||
| 
 | ||||
| 	queries := strings.Split(encodedQuery, "&") | ||||
| 	var filteredQueries []string | ||||
| @ -206,8 +210,12 @@ func doesSignV2Match(r *http.Request) APIErrorCode { | ||||
| 	} | ||||
| 
 | ||||
| 	// r.RequestURI will have raw encoded URI as sent by the client. | ||||
| 	splits := splitStr(r.RequestURI, "?", 2) | ||||
| 	encodedResource, encodedQuery := splits[0], splits[1] | ||||
| 	tokens := strings.SplitN(r.RequestURI, "?", 2) | ||||
| 	encodedResource := tokens[0] | ||||
| 	encodedQuery := "" | ||||
| 	if len(tokens) == 2 { | ||||
| 		encodedQuery = tokens[1] | ||||
| 	} | ||||
| 
 | ||||
| 	expectedAuth := signatureV2(r.Method, encodedResource, encodedQuery, r.Header) | ||||
| 	if v2Auth != expectedAuth { | ||||
|  | ||||
| @ -21,7 +21,6 @@ import ( | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/rpc" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 
 | ||||
| @ -96,39 +95,22 @@ func toStorageErr(err error) error { | ||||
| } | ||||
| 
 | ||||
| // Initialize new storage rpc client. | ||||
| func newStorageRPC(ep *url.URL) (StorageAPI, error) { | ||||
| 	if ep == nil { | ||||
| 		return nil, errInvalidArgument | ||||
| 	} | ||||
| 
 | ||||
| func newStorageRPC(endpoint Endpoint) StorageAPI { | ||||
| 	// Dial minio rpc storage http path. | ||||
| 	rpcPath := path.Join(minioReservedBucketPath, storageRPCPath, getPath(ep)) | ||||
| 	rpcAddr := ep.Host | ||||
| 
 | ||||
| 	rpcPath := path.Join(minioReservedBucketPath, storageRPCPath, endpoint.Path) | ||||
| 	serverCred := serverConfig.GetCredential() | ||||
| 	accessKey := serverCred.AccessKey | ||||
| 	secretKey := serverCred.SecretKey | ||||
| 	if ep.User != nil { | ||||
| 		accessKey = ep.User.Username() | ||||
| 		if password, ok := ep.User.Password(); ok { | ||||
| 			secretKey = password | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	storageAPI := &networkStorage{ | ||||
| 	return &networkStorage{ | ||||
| 		rpcClient: newAuthRPCClient(authConfig{ | ||||
| 			accessKey:        accessKey, | ||||
| 			secretKey:        secretKey, | ||||
| 			serverAddr:       rpcAddr, | ||||
| 			accessKey:        serverCred.AccessKey, | ||||
| 			secretKey:        serverCred.SecretKey, | ||||
| 			serverAddr:       endpoint.Host, | ||||
| 			serviceEndpoint:  rpcPath, | ||||
| 			secureConn:       globalIsSSL, | ||||
| 			serviceName:      "Storage", | ||||
| 			disableReconnect: true, | ||||
| 		}), | ||||
| 	} | ||||
| 
 | ||||
| 	// Returns successfully here. | ||||
| 	return storageAPI, nil | ||||
| } | ||||
| 
 | ||||
| // Stringer interface compatible representation of network device. | ||||
|  | ||||
| @ -23,7 +23,6 @@ import ( | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/rpc" | ||||
| 	"net/url" | ||||
| 	"runtime" | ||||
| 	"testing" | ||||
| ) | ||||
| @ -146,25 +145,15 @@ func (s *TestRPCStorageSuite) SetUpSuite(c *testing.T) { | ||||
| 	listenAddress := s.testServer.Server.Listener.Addr().String() | ||||
| 
 | ||||
| 	for _, ep := range s.testServer.Disks { | ||||
| 		ep.Host = listenAddress | ||||
| 		storageDisk, err := newStorageRPC(ep) | ||||
| 		if err != nil { | ||||
| 			c.Fatal("Unable to initialize RPC client", err) | ||||
| 		// Eventhough s.testServer.Disks is EndpointList, we would need a URLEndpointType here. | ||||
| 		endpoint := ep | ||||
| 		if endpoint.Type() == PathEndpointType { | ||||
| 			endpoint.Scheme = "http" | ||||
| 		} | ||||
| 		endpoint.Host = listenAddress | ||||
| 		storageDisk := newStorageRPC(endpoint) | ||||
| 		s.remoteDisks = append(s.remoteDisks, storageDisk) | ||||
| 	} | ||||
| 	_, err := newStorageRPC(nil) | ||||
| 	if err != errInvalidArgument { | ||||
| 		c.Fatalf("Unexpected error %s, expecting %s", err, errInvalidArgument) | ||||
| 	} | ||||
| 	u, err := url.Parse("http://abcd:abcd123@localhost/mnt/disk") | ||||
| 	if err != nil { | ||||
| 		c.Fatal("Unexpected error", err) | ||||
| 	} | ||||
| 	_, err = newStorageRPC(u) | ||||
| 	if err != nil { | ||||
| 		c.Fatal("Unexpected error", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // No longer used with gocheck, but used in explicit teardown code in | ||||
|  | ||||
| @ -197,30 +197,28 @@ func (s *storageServer) RenameFileHandler(args *RenameFileArgs, reply *AuthRPCRe | ||||
| } | ||||
| 
 | ||||
| // Initialize new storage rpc. | ||||
| func newRPCServer(srvConfig serverCmdConfig) (servers []*storageServer, err error) { | ||||
| 	for _, ep := range srvConfig.endpoints { | ||||
| 		// e.g server:/mnt/disk1 | ||||
| 		if isLocalStorage(ep) { | ||||
| 			// Get the posix path. | ||||
| 			path := getPath(ep) | ||||
| 			var storage StorageAPI | ||||
| 			storage, err = newPosix(path) | ||||
| func newRPCServer(endpoints EndpointList) (servers []*storageServer, err error) { | ||||
| 	for _, endpoint := range endpoints { | ||||
| 		if endpoint.IsLocal { | ||||
| 			storage, err := newPosix(endpoint.Path) | ||||
| 			if err != nil && err != errDiskNotFound { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			servers = append(servers, &storageServer{ | ||||
| 				storage: storage, | ||||
| 				path:    path, | ||||
| 				path:    endpoint.Path, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return servers, nil | ||||
| } | ||||
| 
 | ||||
| // registerStorageRPCRouter - register storage rpc router. | ||||
| func registerStorageRPCRouters(mux *router.Router, srvCmdConfig serverCmdConfig) error { | ||||
| func registerStorageRPCRouters(mux *router.Router, endpoints EndpointList) error { | ||||
| 	// Initialize storage rpc servers for every disk that is hosted on this node. | ||||
| 	storageRPCs, err := newRPCServer(srvCmdConfig) | ||||
| 	storageRPCs, err := newRPCServer(endpoints) | ||||
| 	if err != nil { | ||||
| 		return traceError(err) | ||||
| 	} | ||||
|  | ||||
| @ -17,7 +17,6 @@ | ||||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"net/url" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/minio/minio/pkg/disk" | ||||
| @ -30,7 +29,7 @@ type testStorageRPCServer struct { | ||||
| 	token     string | ||||
| 	diskDirs  []string | ||||
| 	stServer  *storageServer | ||||
| 	endpoints []*url.URL | ||||
| 	endpoints EndpointList | ||||
| } | ||||
| 
 | ||||
| func createTestStorageServer(t *testing.T) *testStorageRPCServer { | ||||
| @ -50,11 +49,7 @@ func createTestStorageServer(t *testing.T) *testStorageRPCServer { | ||||
| 		t.Fatalf("unable to create FS backend, %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unable to parse storage endpoints, %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints := mustGetNewEndpointList(fsDirs...) | ||||
| 	storageDisks, err := initStorageDisks(endpoints) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unable to initialize storage disks, %s", err) | ||||
|  | ||||
| @ -91,11 +91,7 @@ func prepareXL() (ObjectLayer, []string, error) { | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		removeRoots(fsDirs) | ||||
| 		return nil, nil, err | ||||
| @ -180,12 +176,12 @@ func isSameType(obj1, obj2 interface{}) bool { | ||||
| //   defer s.Stop() | ||||
| type TestServer struct { | ||||
| 	Root      string | ||||
| 	Disks     []*url.URL | ||||
| 	Disks     EndpointList | ||||
| 	AccessKey string | ||||
| 	SecretKey string | ||||
| 	Server    *httptest.Server | ||||
| 	Obj       ObjectLayer | ||||
| 	SrvCmdCfg serverCmdConfig | ||||
| 	endpoints EndpointList | ||||
| } | ||||
| 
 | ||||
| // UnstartedTestServer - Configures a temp FS/XL backend, | ||||
| @ -210,50 +206,31 @@ func UnstartedTestServer(t TestErrHandler, instanceType string) TestServer { | ||||
| 	credentials := serverConfig.GetCredential() | ||||
| 
 | ||||
| 	testServer.Obj = objLayer | ||||
| 	testServer.Disks, err = parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %v", err) | ||||
| 	} | ||||
| 	testServer.Disks = mustGetNewEndpointList(disks...) | ||||
| 	testServer.Root = root | ||||
| 	testServer.AccessKey = credentials.AccessKey | ||||
| 	testServer.SecretKey = credentials.SecretKey | ||||
| 
 | ||||
| 	srvCmdCfg := serverCmdConfig{ | ||||
| 		endpoints: testServer.Disks, | ||||
| 	} | ||||
| 
 | ||||
| 	httpHandler, err := configureServerHandler( | ||||
| 		srvCmdCfg, | ||||
| 	) | ||||
| 	httpHandler, err := configureServerHandler(testServer.Disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to configure one of the RPC services <ERROR> %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Run TestServer. | ||||
| 	testServer.Server = httptest.NewUnstartedServer(httpHandler) | ||||
| 	// obtain server address. | ||||
| 	srvCmdCfg.serverAddr = testServer.Server.Listener.Addr().String() | ||||
| 
 | ||||
| 	globalObjLayerMutex.Lock() | ||||
| 	globalObjectAPI = objLayer | ||||
| 	globalObjLayerMutex.Unlock() | ||||
| 
 | ||||
| 	// initialize peer rpc | ||||
| 	host, port, err := net.SplitHostPort(srvCmdCfg.serverAddr) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Early setup error:", err) | ||||
| 	} | ||||
| 	host, port := mustSplitHostPort(testServer.Server.Listener.Addr().String()) | ||||
| 	globalMinioHost = host | ||||
| 	globalMinioPort = port | ||||
| 	globalMinioAddr = getLocalAddress(srvCmdCfg) | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Early setup error:", err) | ||||
| 	} | ||||
| 	initGlobalS3Peers(endpoints) | ||||
| 	globalMinioAddr = getEndpointsLocalAddr(testServer.Disks) | ||||
| 	initGlobalS3Peers(testServer.Disks) | ||||
| 
 | ||||
| 	return testServer | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // testServerCertPEM and testServerKeyPEM are generated by | ||||
| @ -339,10 +316,10 @@ func StartTestServer(t TestErrHandler, instanceType string) TestServer { | ||||
| 
 | ||||
| // Initializes storage RPC endpoints. | ||||
| // The object Layer will be a temp back used for testing purpose. | ||||
| func initTestStorageRPCEndPoint(srvCmdConfig serverCmdConfig) http.Handler { | ||||
| func initTestStorageRPCEndPoint(endpoints EndpointList) http.Handler { | ||||
| 	// Initialize router. | ||||
| 	muxRouter := router.NewRouter() | ||||
| 	registerStorageRPCRouters(muxRouter, srvCmdConfig) | ||||
| 	registerStorageRPCRouters(muxRouter, endpoints) | ||||
| 	return muxRouter | ||||
| } | ||||
| 
 | ||||
| @ -354,10 +331,6 @@ func StartTestStorageRPCServer(t TestErrHandler, instanceType string, diskN int) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Failed to create disks for the backend") | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("%s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	root, err := newTestConfig(globalMinioDefaultRegion) | ||||
| 	if err != nil { | ||||
| @ -369,15 +342,14 @@ func StartTestStorageRPCServer(t TestErrHandler, instanceType string, diskN int) | ||||
| 	// Get credential. | ||||
| 	credentials := serverConfig.GetCredential() | ||||
| 
 | ||||
| 	endpoints := mustGetNewEndpointList(disks...) | ||||
| 	testRPCServer.Root = root | ||||
| 	testRPCServer.Disks = endpoints | ||||
| 	testRPCServer.AccessKey = credentials.AccessKey | ||||
| 	testRPCServer.SecretKey = credentials.SecretKey | ||||
| 
 | ||||
| 	// Run TestServer. | ||||
| 	testRPCServer.Server = httptest.NewServer(initTestStorageRPCEndPoint(serverCmdConfig{ | ||||
| 		endpoints: endpoints, | ||||
| 	})) | ||||
| 	testRPCServer.Server = httptest.NewServer(initTestStorageRPCEndPoint(endpoints)) | ||||
| 	return testRPCServer | ||||
| } | ||||
| 
 | ||||
| @ -389,10 +361,6 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer { | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Failed to create disks for the backend") | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("%s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	root, err := newTestConfig(globalMinioDefaultRegion) | ||||
| 	if err != nil { | ||||
| @ -404,6 +372,7 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer { | ||||
| 	// Get credential. | ||||
| 	credentials := serverConfig.GetCredential() | ||||
| 
 | ||||
| 	endpoints := mustGetNewEndpointList(disks...) | ||||
| 	testRPCServer.Root = root | ||||
| 	testRPCServer.Disks = endpoints | ||||
| 	testRPCServer.AccessKey = credentials.AccessKey | ||||
| @ -420,13 +389,9 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer { | ||||
| 	testRPCServer.Obj = objLayer | ||||
| 	globalObjLayerMutex.Unlock() | ||||
| 
 | ||||
| 	srvCfg := serverCmdConfig{ | ||||
| 		endpoints: endpoints, | ||||
| 	} | ||||
| 
 | ||||
| 	mux := router.NewRouter() | ||||
| 	// need storage layer for bucket config storage. | ||||
| 	registerStorageRPCRouters(mux, srvCfg) | ||||
| 	registerStorageRPCRouters(mux, endpoints) | ||||
| 	// need API layer to send requests, etc. | ||||
| 	registerAPIRouter(mux) | ||||
| 	// module being tested is Peer RPCs router. | ||||
| @ -436,7 +401,7 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer { | ||||
| 	testRPCServer.Server = httptest.NewServer(mux) | ||||
| 
 | ||||
| 	// initialize remainder of serverCmdConfig | ||||
| 	testRPCServer.SrvCmdCfg = srvCfg | ||||
| 	testRPCServer.endpoints = endpoints | ||||
| 
 | ||||
| 	return testRPCServer | ||||
| } | ||||
| @ -481,7 +446,7 @@ func resetGlobalEventnotify() { | ||||
| } | ||||
| 
 | ||||
| func resetGlobalEndpoints() { | ||||
| 	globalEndpoints = []*url.URL{} | ||||
| 	globalEndpoints = EndpointList{} | ||||
| } | ||||
| 
 | ||||
| func resetGlobalIsXL() { | ||||
| @ -1659,7 +1624,7 @@ func getRandomDisks(N int) ([]string, error) { | ||||
| } | ||||
| 
 | ||||
| // initObjectLayer - Instantiates object layer and returns it. | ||||
| func initObjectLayer(endpoints []*url.URL) (ObjectLayer, []StorageAPI, error) { | ||||
| func initObjectLayer(endpoints EndpointList) (ObjectLayer, []StorageAPI, error) { | ||||
| 	storageDisks, err := initStorageDisks(endpoints) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| @ -1738,12 +1703,8 @@ func prepareXLStorageDisks(t *testing.T) ([]StorageAPI, []string) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected error: ", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected error: ", err) | ||||
| 	} | ||||
| 
 | ||||
| 	_, storageDisks, err := initObjectLayer(endpoints) | ||||
| 	_, storageDisks, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		removeRoots(fsDirs) | ||||
| 		t.Fatal("Unable to initialize storage disks", err) | ||||
| @ -2077,11 +2038,7 @@ func ExecObjectLayerStaleFilesTest(t *testing.T, objTest objTestStaleFilesType) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Initialization of disks for XL setup: %s", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(erasureDisks) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Initialization of disks for XL setup: %s", err) | ||||
| 	} | ||||
| 	objLayer, _, err := initObjectLayer(endpoints) | ||||
| 	objLayer, _, err := initObjectLayer(mustGetNewEndpointList(erasureDisks...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Initialization of object layer failed for XL setup: %s", err) | ||||
| 	} | ||||
| @ -2380,3 +2337,27 @@ func generateTLSCertKey(host string) ([]byte, []byte, error) { | ||||
| 
 | ||||
| 	return certOut.Bytes(), keyOut.Bytes(), nil | ||||
| } | ||||
| 
 | ||||
| func mustGetNewEndpointList(args ...string) (endpoints EndpointList) { | ||||
| 	if len(args) == 1 { | ||||
| 		endpoint, err := NewEndpoint(args[0]) | ||||
| 		fatalIf(err, "unable to create new endpoint") | ||||
| 		endpoints = append(endpoints, endpoint) | ||||
| 	} else { | ||||
| 		var err error | ||||
| 		endpoints, err = NewEndpointList(args...) | ||||
| 		fatalIf(err, "unable to create new endpoint list") | ||||
| 	} | ||||
| 
 | ||||
| 	return endpoints | ||||
| } | ||||
| 
 | ||||
| func getEndpointsLocalAddr(endpoints EndpointList) string { | ||||
| 	for _, endpoint := range endpoints { | ||||
| 		if endpoint.IsLocal && endpoint.Type() == URLEndpointType { | ||||
| 			return endpoint.Host | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return globalMinioHost + ":" + globalMinioPort | ||||
| } | ||||
|  | ||||
| @ -164,10 +164,7 @@ func TestTreeWalk(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tmp directory: %s", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints([]string{fsDir}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %s", err) | ||||
| 	} | ||||
| 	endpoints := mustGetNewEndpointList(fsDir) | ||||
| 	disk, err := newStorageAPI(endpoints[0]) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create StorageAPI: %s", err) | ||||
| @ -205,10 +202,7 @@ func TestTreeWalkTimeout(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create tmp directory: %s", err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints([]string{fsDir}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %s", err) | ||||
| 	} | ||||
| 	endpoints := mustGetNewEndpointList(fsDir) | ||||
| 	disk, err := newStorageAPI(endpoints[0]) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create StorageAPI: %s", err) | ||||
| @ -285,18 +279,15 @@ func TestListDir(t *testing.T) { | ||||
| 		t.Errorf("Unable to create tmp directory: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints([]string{fsDir1, fsDir2}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Create two StorageAPIs disk1 and disk2. | ||||
| 	endpoints := mustGetNewEndpointList(fsDir1) | ||||
| 	disk1, err := newStorageAPI(endpoints[0]) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unable to create StorageAPI: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	disk2, err := newStorageAPI(endpoints[1]) | ||||
| 	endpoints = mustGetNewEndpointList(fsDir2) | ||||
| 	disk2, err := newStorageAPI(endpoints[0]) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unable to create StorageAPI: %s", err) | ||||
| 	} | ||||
| @ -366,10 +357,7 @@ func TestRecursiveTreeWalk(t *testing.T) { | ||||
| 		t.Fatalf("Unable to create tmp directory: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints([]string{fsDir1}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %s", err) | ||||
| 	} | ||||
| 	endpoints := mustGetNewEndpointList(fsDir1) | ||||
| 	disk1, err := newStorageAPI(endpoints[0]) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create StorageAPI: %s", err) | ||||
| @ -476,10 +464,7 @@ func TestSortedness(t *testing.T) { | ||||
| 		t.Errorf("Unable to create tmp directory: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints([]string{fsDir1}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %s", err) | ||||
| 	} | ||||
| 	endpoints := mustGetNewEndpointList(fsDir1) | ||||
| 	disk1, err := newStorageAPI(endpoints[0]) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create StorageAPI: %s", err) | ||||
| @ -554,10 +539,7 @@ func TestTreeWalkIsEnd(t *testing.T) { | ||||
| 		t.Errorf("Unable to create tmp directory: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints([]string{fsDir1}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %s", err) | ||||
| 	} | ||||
| 	endpoints := mustGetNewEndpointList(fsDir1) | ||||
| 	disk1, err := newStorageAPI(endpoints[0]) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to create StorageAPI: %s", err) | ||||
|  | ||||
| @ -1,99 +0,0 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2016 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 ( | ||||
| 	"net/url" | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| // TestSortByHostPath - tests if ordering of urls are based on | ||||
| // host+path concatenated. | ||||
| func TestSortByHostPath(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		given    []string | ||||
| 		expected []*url.URL | ||||
| 	}{ | ||||
| 		{ | ||||
| 			given: []string{ | ||||
| 				"http://abcd.com/a/b/d", | ||||
| 				"http://abcd.com/a/b/c", | ||||
| 				"http://abcd.com/a/b/e", | ||||
| 			}, | ||||
| 			expected: []*url.URL{ | ||||
| 				{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "abcd.com:9000", | ||||
| 					Path:   "/a/b/c", | ||||
| 				}, | ||||
| 				{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "abcd.com:9000", | ||||
| 					Path:   "/a/b/d", | ||||
| 				}, | ||||
| 				{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "abcd.com:9000", | ||||
| 					Path:   "/a/b/e", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			given: []string{ | ||||
| 				"http://defg.com/a/b/c", | ||||
| 				"http://abcd.com/a/b/c", | ||||
| 				"http://hijk.com/a/b/c", | ||||
| 			}, | ||||
| 			expected: []*url.URL{ | ||||
| 				{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "abcd.com:9000", | ||||
| 					Path:   "/a/b/c", | ||||
| 				}, | ||||
| 				{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "defg.com:9000", | ||||
| 					Path:   "/a/b/c", | ||||
| 				}, | ||||
| 				{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "hijk.com:9000", | ||||
| 					Path:   "/a/b/c", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	saveGlobalPort := globalMinioPort | ||||
| 	globalMinioPort = "9000" | ||||
| 	for i, test := range testCases { | ||||
| 		eps, err := parseStorageEndpoints(test.given) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Test %d - Failed to parse storage endpoint %v", i+1, err) | ||||
| 		} | ||||
| 		sort.Sort(byHostPath(eps)) | ||||
| 		if !sort.IsSorted(byHostPath(eps)) { | ||||
| 			t.Errorf("Test %d - Expected order %v but got %v", i+1, test.expected, eps) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(eps, test.expected) { | ||||
| 			t.Errorf("Test %d - Expected order %v but got %v", i+1, test.expected, eps) | ||||
| 		} | ||||
| 	} | ||||
| 	globalMinioPort = saveGlobalPort | ||||
| } | ||||
							
								
								
									
										70
									
								
								cmd/utils.go
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								cmd/utils.go
									
									
									
									
									
								
							| @ -45,44 +45,6 @@ func cloneHeader(h http.Header) http.Header { | ||||
| 	return h2 | ||||
| } | ||||
| 
 | ||||
| // checkDuplicates - function to validate if there are duplicates in a slice of strings. | ||||
| func checkDuplicateStrings(list []string) error { | ||||
| 	// Empty lists are not allowed. | ||||
| 	if len(list) == 0 { | ||||
| 		return errInvalidArgument | ||||
| 	} | ||||
| 	// Empty keys are not allowed. | ||||
| 	for _, key := range list { | ||||
| 		if key == "" { | ||||
| 			return errInvalidArgument | ||||
| 		} | ||||
| 	} | ||||
| 	listMaps := make(map[string]int) | ||||
| 	// Navigate through each configs and count the entries. | ||||
| 	for _, key := range list { | ||||
| 		listMaps[key]++ | ||||
| 	} | ||||
| 	// Validate if there are any duplicate counts. | ||||
| 	for key, count := range listMaps { | ||||
| 		if count != 1 { | ||||
| 			return fmt.Errorf("Duplicate key: \"%s\" found of count: \"%d\"", key, count) | ||||
| 		} | ||||
| 	} | ||||
| 	// No duplicates. | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // splitStr splits a string into n parts, empty strings are added | ||||
| // if we are not able to reach n elements | ||||
| func splitStr(path, sep string, n int) []string { | ||||
| 	splits := strings.SplitN(path, sep, n) | ||||
| 	// Add empty strings if we found elements less than nr | ||||
| 	for i := n - len(splits); i > 0; i-- { | ||||
| 		splits = append(splits, "") | ||||
| 	} | ||||
| 	return splits | ||||
| } | ||||
| 
 | ||||
| // Convert url path into bucket and object name. | ||||
| func urlPath2BucketObjectName(u *url.URL) (bucketName, objectName string) { | ||||
| 	if u == nil { | ||||
| @ -95,10 +57,11 @@ func urlPath2BucketObjectName(u *url.URL) (bucketName, objectName string) { | ||||
| 
 | ||||
| 	// Split urlpath using slash separator into a given number of | ||||
| 	// expected tokens. | ||||
| 	tokens := splitStr(urlPath, slashSeparator, 2) | ||||
| 
 | ||||
| 	// Extract bucket and objects. | ||||
| 	bucketName, objectName = tokens[0], tokens[1] | ||||
| 	tokens := strings.SplitN(urlPath, slashSeparator, 2) | ||||
| 	bucketName = tokens[0] | ||||
| 	if len(tokens) == 2 { | ||||
| 		objectName = tokens[1] | ||||
| 	} | ||||
| 
 | ||||
| 	// Success. | ||||
| 	return bucketName, objectName | ||||
| @ -110,29 +73,6 @@ const ( | ||||
| 	httpsScheme = "https" | ||||
| ) | ||||
| 
 | ||||
| // checkDuplicates - function to validate if there are duplicates in a slice of endPoints. | ||||
| func checkDuplicateEndpoints(endpoints []*url.URL) error { | ||||
| 	var strs []string | ||||
| 	for _, ep := range endpoints { | ||||
| 		strs = append(strs, ep.String()) | ||||
| 	} | ||||
| 	return checkDuplicateStrings(strs) | ||||
| } | ||||
| 
 | ||||
| // Find local node through the command line arguments. Returns in `host:port` format. | ||||
| func getLocalAddress(srvCmdConfig serverCmdConfig) string { | ||||
| 	if !globalIsDistXL { | ||||
| 		return srvCmdConfig.serverAddr | ||||
| 	} | ||||
| 	for _, ep := range srvCmdConfig.endpoints { | ||||
| 		// Validates if remote endpoint is local. | ||||
| 		if isLocalStorage(ep) { | ||||
| 			return ep.Host | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| // xmlDecoder provide decoded value in xml. | ||||
| func xmlDecoder(body io.Reader, v interface{}, size int64) error { | ||||
| 	var lbody io.Reader | ||||
|  | ||||
| @ -18,12 +18,9 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"reflect" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
| @ -52,57 +49,6 @@ func TestCloneHeader(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests check duplicates function. | ||||
| func TestCheckDuplicates(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		list       []string | ||||
| 		err        error | ||||
| 		shouldPass bool | ||||
| 	}{ | ||||
| 		// Test 1 - for '/tmp/1' repeated twice. | ||||
| 		{ | ||||
| 			list:       []string{"/tmp/1", "/tmp/1", "/tmp/2", "/tmp/3"}, | ||||
| 			err:        fmt.Errorf("Duplicate key: \"/tmp/1\" found of count: \"2\""), | ||||
| 			shouldPass: false, | ||||
| 		}, | ||||
| 		// Test 2 - for '/tmp/1' repeated thrice. | ||||
| 		{ | ||||
| 			list:       []string{"/tmp/1", "/tmp/1", "/tmp/1", "/tmp/3"}, | ||||
| 			err:        fmt.Errorf("Duplicate key: \"/tmp/1\" found of count: \"3\""), | ||||
| 			shouldPass: false, | ||||
| 		}, | ||||
| 		// Test 3 - empty string. | ||||
| 		{ | ||||
| 			list:       []string{""}, | ||||
| 			err:        errInvalidArgument, | ||||
| 			shouldPass: false, | ||||
| 		}, | ||||
| 		// Test 4 - empty string. | ||||
| 		{ | ||||
| 			list:       nil, | ||||
| 			err:        errInvalidArgument, | ||||
| 			shouldPass: false, | ||||
| 		}, | ||||
| 		// Test 5 - non repeated strings. | ||||
| 		{ | ||||
| 			list:       []string{"/tmp/1", "/tmp/2", "/tmp/3"}, | ||||
| 			err:        nil, | ||||
| 			shouldPass: true, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	// Validate if function runs as expected. | ||||
| 	for i, test := range tests { | ||||
| 		err := checkDuplicateStrings(test.list) | ||||
| 		if test.shouldPass && err != test.err { | ||||
| 			t.Errorf("Test: %d, Expected %s got %s", i+1, test.err, err) | ||||
| 		} | ||||
| 		if !test.shouldPass && err.Error() != test.err.Error() { | ||||
| 			t.Errorf("Test: %d, Expected %s got %s", i+1, test.err, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests maximum object size. | ||||
| func TestMaxObjectSize(t *testing.T) { | ||||
| 	sizes := []struct { | ||||
| @ -275,122 +221,6 @@ func TestStartProfiler(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests fetch local address. | ||||
| func TestLocalAddress(t *testing.T) { | ||||
| 	if runtime.GOOS == globalWindowsOSName { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	currentIsDistXL := globalIsDistXL | ||||
| 	defer func() { | ||||
| 		globalIsDistXL = currentIsDistXL | ||||
| 	}() | ||||
| 
 | ||||
| 	// need to set this to avoid stale values from other tests. | ||||
| 	globalMinioPort = "9000" | ||||
| 	globalMinioHost = "" | ||||
| 	testCases := []struct { | ||||
| 		isDistXL     bool | ||||
| 		srvCmdConfig serverCmdConfig | ||||
| 		localAddr    string | ||||
| 	}{ | ||||
| 		// Test 1 - local address is found. | ||||
| 		{ | ||||
| 			isDistXL: true, | ||||
| 			srvCmdConfig: serverCmdConfig{ | ||||
| 				endpoints: []*url.URL{{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "localhost:9000", | ||||
| 					Path:   "/mnt/disk1", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.1.2:9000", | ||||
| 					Path:   "/mnt/disk2", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.2.1:9000", | ||||
| 					Path:   "/mnt/disk3", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.2.2:9000", | ||||
| 					Path:   "/mnt/disk4", | ||||
| 				}}, | ||||
| 			}, | ||||
| 			localAddr: net.JoinHostPort("localhost", globalMinioPort), | ||||
| 		}, | ||||
| 		// Test 2 - local address is everything. | ||||
| 		{ | ||||
| 			isDistXL: false, | ||||
| 			srvCmdConfig: serverCmdConfig{ | ||||
| 				serverAddr: net.JoinHostPort("", globalMinioPort), | ||||
| 				endpoints: []*url.URL{{ | ||||
| 					Path: "/mnt/disk1", | ||||
| 				}, { | ||||
| 					Path: "/mnt/disk2", | ||||
| 				}, { | ||||
| 					Path: "/mnt/disk3", | ||||
| 				}, { | ||||
| 					Path: "/mnt/disk4", | ||||
| 				}}, | ||||
| 			}, | ||||
| 			localAddr: net.JoinHostPort("", globalMinioPort), | ||||
| 		}, | ||||
| 		// Test 3 - local address is not found. | ||||
| 		{ | ||||
| 			isDistXL: true, | ||||
| 			srvCmdConfig: serverCmdConfig{ | ||||
| 				endpoints: []*url.URL{{ | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.1.1:9000", | ||||
| 					Path:   "/mnt/disk2", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.1.2:9000", | ||||
| 					Path:   "/mnt/disk2", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.2.1:9000", | ||||
| 					Path:   "/mnt/disk3", | ||||
| 				}, { | ||||
| 					Scheme: httpScheme, | ||||
| 					Host:   "1.1.2.2:9000", | ||||
| 					Path:   "/mnt/disk4", | ||||
| 				}}, | ||||
| 			}, | ||||
| 			localAddr: "", | ||||
| 		}, | ||||
| 		// Test 4 - in case of FS mode, with SSL, the host | ||||
| 		// name is specified in the --address option on the | ||||
| 		// server command line. | ||||
| 		{ | ||||
| 			isDistXL: false, | ||||
| 			srvCmdConfig: serverCmdConfig{ | ||||
| 				serverAddr: "play.minio.io:9000", | ||||
| 				endpoints: []*url.URL{{ | ||||
| 					Path: "/mnt/disk1", | ||||
| 				}, { | ||||
| 					Path: "/mnt/disk2", | ||||
| 				}, { | ||||
| 					Path: "/mnt/disk3", | ||||
| 				}, { | ||||
| 					Path: "/mnt/disk4", | ||||
| 				}}, | ||||
| 			}, | ||||
| 			localAddr: "play.minio.io:9000", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	// Validates fetching local address. | ||||
| 	for i, testCase := range testCases { | ||||
| 		globalIsDistXL = testCase.isDistXL | ||||
| 		localAddr := getLocalAddress(testCase.srvCmdConfig) | ||||
| 		if localAddr != testCase.localAddr { | ||||
| 			t.Fatalf("Test %d: Expected %s, got %s", i+1, testCase.localAddr, localAddr) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // TestCheckURL tests valid url. | ||||
| func TestCheckURL(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
|  | ||||
| @ -37,13 +37,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Everything is fine, should return nil | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -59,13 +54,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Disks 0..15 are nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -84,13 +74,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk returns Faulty Disk | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -112,13 +97,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk is not found, heal corrupted disks should return nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -134,13 +114,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Remove format.json of all disks | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -160,13 +135,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Corrupted format json in one disk | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -186,13 +156,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Remove format.json on 3 disks. | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -212,13 +177,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk is not found, heal corrupted disks should return nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -246,13 +206,8 @@ func TestHealFormatXL(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk is not found, heal corrupted disks should return nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -286,13 +241,8 @@ func TestUndoMakeBucket(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Remove format.json on 16 disks. | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -331,13 +281,8 @@ func TestQuickHeal(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Remove format.json on 16 disks. | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -382,13 +327,8 @@ func TestQuickHeal(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk is not found, heal corrupted disks should return nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -404,13 +344,8 @@ func TestQuickHeal(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err = parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// One disk is not found, heal corrupted disks should return nil | ||||
| 	obj, _, err = initObjectLayer(endpoints) | ||||
| 	obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -441,12 +376,7 @@ func TestListBucketsHeal(t *testing.T) { | ||||
| 	} | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -505,13 +435,8 @@ func TestHealObjectXL(t *testing.T) { | ||||
| 
 | ||||
| 	defer removeRoots(fsDirs) | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Everything is fine, should return nil | ||||
| 	obj, _, err := initObjectLayer(endpoints) | ||||
| 	obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| @ -392,11 +392,7 @@ func TestShuffleDisks(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	endpoints, err := parseStorageEndpoints(disks) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	objLayer, _, err := initObjectLayer(endpoints) | ||||
| 	objLayer, _, err := initObjectLayer(mustGetNewEndpointList(disks...)) | ||||
| 	if err != nil { | ||||
| 		removeRoots(disks) | ||||
| 		t.Fatal(err) | ||||
|  | ||||
| @ -50,12 +50,7 @@ func TestStorageInfo(t *testing.T) { | ||||
| 		t.Fatalf("Diskinfo total values should be greater 0") | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(fsDirs) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unexpected error %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	storageDisks, err := initStorageDisks(endpoints) | ||||
| 	storageDisks, err := initStorageDisks(mustGetNewEndpointList(fsDirs...)) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected error: ", err) | ||||
| 	} | ||||
| @ -145,11 +140,7 @@ func TestNewXL(t *testing.T) { | ||||
| 		t.Fatalf("Unable to initialize erasure, %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints, err := parseStorageEndpoints(erasureDisks) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Unable to initialize erasure, %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	endpoints := mustGetNewEndpointList(erasureDisks...) | ||||
| 	storageDisks, err := initStorageDisks(endpoints) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("Unexpected error: ", err) | ||||
|  | ||||
							
								
								
									
										8
									
								
								vendor/github.com/minio/minio-go/pkg/set/stringset.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/minio/minio-go/pkg/set/stringset.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -25,8 +25,8 @@ import ( | ||||
| // StringSet - uses map as set of strings. | ||||
| type StringSet map[string]struct{} | ||||
| 
 | ||||
| // keys - returns StringSet keys. | ||||
| func (set StringSet) keys() []string { | ||||
| // ToSlice - returns StringSet as string slice. | ||||
| func (set StringSet) ToSlice() []string { | ||||
| 	keys := make([]string, 0, len(set)) | ||||
| 	for k := range set { | ||||
| 		keys = append(keys, k) | ||||
| @ -141,7 +141,7 @@ func (set StringSet) Union(sset StringSet) StringSet { | ||||
| 
 | ||||
| // MarshalJSON - converts to JSON data. | ||||
| func (set StringSet) MarshalJSON() ([]byte, error) { | ||||
| 	return json.Marshal(set.keys()) | ||||
| 	return json.Marshal(set.ToSlice()) | ||||
| } | ||||
| 
 | ||||
| // UnmarshalJSON - parses JSON data and creates new set with it. | ||||
| @ -169,7 +169,7 @@ func (set *StringSet) UnmarshalJSON(data []byte) error { | ||||
| 
 | ||||
| // String - returns printable string of the set. | ||||
| func (set StringSet) String() string { | ||||
| 	return fmt.Sprintf("%s", set.keys()) | ||||
| 	return fmt.Sprintf("%s", set.ToSlice()) | ||||
| } | ||||
| 
 | ||||
| // NewStringSet - creates new string set. | ||||
|  | ||||
							
								
								
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							| @ -227,10 +227,10 @@ | ||||
| 			"revisionTime": "2016-12-20T20:43:13Z" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"checksumSHA1": "A8QOw1aWwc+RtjGozY0XeS5varo=", | ||||
| 			"checksumSHA1": "maUy+dbN6VfTTnfErrAW2lLit1w=", | ||||
| 			"path": "github.com/minio/minio-go/pkg/set", | ||||
| 			"revision": "9e734013294ab153b0bdbe182738bcddd46f1947", | ||||
| 			"revisionTime": "2016-08-18T00:31:20Z" | ||||
| 			"revision": "7a3619e41885dcbcfafee193c10eb80530c2be53", | ||||
| 			"revisionTime": "2017-02-17T20:03:45Z" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"checksumSHA1": "URVle4qtadmW9w9BulDRHY3kxnA=", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user