mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05: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
|
// Initialize boot time
|
||||||
globalBootTime = UTCNow()
|
globalBootTime = UTCNow()
|
||||||
|
|
||||||
// Set globalEndpoints for a single node XL setup.
|
globalEndpoints = mustGetNewEndpointList(xlDirs...)
|
||||||
for _, xlDir := range xlDirs {
|
|
||||||
globalEndpoints = append(globalEndpoints, &url.URL{
|
|
||||||
Path: xlDir,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set globalIsXL to indicate that the setup uses an erasure code backend.
|
// Set globalIsXL to indicate that the setup uses an erasure code backend.
|
||||||
globalIsXL = true
|
globalIsXL = true
|
||||||
@ -301,14 +296,8 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
|||||||
// Initialize admin peers to make admin RPC calls. Note: In a
|
// Initialize admin peers to make admin RPC calls. Note: In a
|
||||||
// single node setup, this degenerates to a simple function
|
// single node setup, this degenerates to a simple function
|
||||||
// call under the hood.
|
// call under the hood.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"})
|
globalMinioAddr = "127.0.0.1:9000"
|
||||||
if err != nil {
|
initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1"))
|
||||||
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)
|
|
||||||
|
|
||||||
// Setting up a go routine to simulate ServerMux's
|
// Setting up a go routine to simulate ServerMux's
|
||||||
// handleServiceSignals for stop and restart commands.
|
// 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
|
// Initialize admin peers to make admin RPC calls. Note: In a
|
||||||
// single node setup, this degenerates to a simple function
|
// single node setup, this degenerates to a simple function
|
||||||
// call under the hood.
|
// call under the hood.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"})
|
globalMinioAddr = "127.0.0.1:9000"
|
||||||
if err != nil {
|
initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1"))
|
||||||
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)
|
|
||||||
|
|
||||||
credentials := serverConfig.GetCredential()
|
credentials := serverConfig.GetCredential()
|
||||||
var body []byte
|
var body []byte
|
||||||
@ -455,14 +438,8 @@ func TestListLocksHandler(t *testing.T) {
|
|||||||
defer adminTestBed.TearDown()
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
// Initialize admin peers to make admin RPC calls.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"})
|
globalMinioAddr = "127.0.0.1:9000"
|
||||||
if err != nil {
|
initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1"))
|
||||||
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)
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
bucket string
|
bucket string
|
||||||
@ -530,11 +507,7 @@ func TestClearLocksHandler(t *testing.T) {
|
|||||||
defer adminTestBed.TearDown()
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
// Initialize admin peers to make admin RPC calls.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"})
|
initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1"))
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to parse storage end point - %v", err)
|
|
||||||
}
|
|
||||||
initGlobalAdminPeers(eps)
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
bucket string
|
bucket string
|
||||||
@ -1238,14 +1211,8 @@ func TestGetConfigHandler(t *testing.T) {
|
|||||||
defer adminTestBed.TearDown()
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
// Initialize admin peers to make admin RPC calls.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"})
|
globalMinioAddr = "127.0.0.1:9000"
|
||||||
if err != nil {
|
initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1"))
|
||||||
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)
|
|
||||||
|
|
||||||
// Prepare query params for get-config mgmt REST API.
|
// Prepare query params for get-config mgmt REST API.
|
||||||
queryVal := url.Values{}
|
queryVal := url.Values{}
|
||||||
@ -1273,14 +1240,8 @@ func TestSetConfigHandler(t *testing.T) {
|
|||||||
defer adminTestBed.TearDown()
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
// Initialize admin peers to make admin RPC calls.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"})
|
globalMinioAddr = "127.0.0.1:9000"
|
||||||
if err != nil {
|
initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1"))
|
||||||
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)
|
|
||||||
|
|
||||||
// SetConfigHandler restarts minio setup - need to start a
|
// SetConfigHandler restarts minio setup - need to start a
|
||||||
// signal receiver to receive on globalServiceSignalCh.
|
// signal receiver to receive on globalServiceSignalCh.
|
||||||
@ -1321,14 +1282,8 @@ func TestAdminServerInfo(t *testing.T) {
|
|||||||
defer adminTestBed.TearDown()
|
defer adminTestBed.TearDown()
|
||||||
|
|
||||||
// Initialize admin peers to make admin RPC calls.
|
// Initialize admin peers to make admin RPC calls.
|
||||||
eps, err := parseStorageEndpoints([]string{"http://127.0.0.1"})
|
globalMinioAddr = "127.0.0.1:9000"
|
||||||
if err != nil {
|
initGlobalAdminPeers(mustGetNewEndpointList("http://127.0.0.1:9000/d1"))
|
||||||
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)
|
|
||||||
|
|
||||||
// Prepare query params for set-config mgmt REST API.
|
// Prepare query params for set-config mgmt REST API.
|
||||||
queryVal := url.Values{}
|
queryVal := url.Values{}
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -28,6 +28,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -211,52 +213,43 @@ type adminPeer struct {
|
|||||||
type adminPeers []adminPeer
|
type adminPeers []adminPeer
|
||||||
|
|
||||||
// makeAdminPeers - helper function to construct a collection of adminPeer.
|
// makeAdminPeers - helper function to construct a collection of adminPeer.
|
||||||
func makeAdminPeers(eps []*url.URL) adminPeers {
|
func makeAdminPeers(endpoints EndpointList) (adminPeerList adminPeers) {
|
||||||
var servicePeers []adminPeer
|
thisPeer := globalMinioAddr
|
||||||
|
if globalMinioHost == "" {
|
||||||
// map to store peers that are already added to ret
|
thisPeer = net.JoinHostPort("localhost", globalMinioPort)
|
||||||
seenAddr := make(map[string]bool)
|
}
|
||||||
|
adminPeerList = append(adminPeerList, adminPeer{
|
||||||
// add local (self) as peer in the array
|
thisPeer,
|
||||||
servicePeers = append(servicePeers, adminPeer{
|
|
||||||
globalMinioAddr,
|
|
||||||
localAdminClient{},
|
localAdminClient{},
|
||||||
})
|
})
|
||||||
seenAddr[globalMinioAddr] = true
|
|
||||||
|
|
||||||
serverCred := serverConfig.GetCredential()
|
hostSet := set.CreateStringSet(globalMinioAddr)
|
||||||
// iterate over endpoints to find new remote peers and add
|
cred := serverConfig.GetCredential()
|
||||||
// them to ret.
|
serviceEndpoint := path.Join(minioReservedBucketPath, adminPath)
|
||||||
for _, ep := range eps {
|
for _, host := range GetRemotePeers(endpoints) {
|
||||||
if ep.Host == "" {
|
if hostSet.Contains(host) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
hostSet.Add(host)
|
||||||
// Check if the remote host has been added already
|
adminPeerList = append(adminPeerList, adminPeer{
|
||||||
if !seenAddr[ep.Host] {
|
addr: host,
|
||||||
cfg := authConfig{
|
cmdRunner: &remoteAdminClient{newAuthRPCClient(authConfig{
|
||||||
accessKey: serverCred.AccessKey,
|
accessKey: cred.AccessKey,
|
||||||
secretKey: serverCred.SecretKey,
|
secretKey: cred.SecretKey,
|
||||||
serverAddr: ep.Host,
|
serverAddr: host,
|
||||||
|
serviceEndpoint: serviceEndpoint,
|
||||||
secureConn: globalIsSSL,
|
secureConn: globalIsSSL,
|
||||||
serviceEndpoint: path.Join(minioReservedBucketPath, adminPath),
|
|
||||||
serviceName: "Admin",
|
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.
|
// Initialize global adminPeer collection.
|
||||||
func initGlobalAdminPeers(eps []*url.URL) {
|
func initGlobalAdminPeers(endpoints EndpointList) {
|
||||||
globalAdminPeers = makeAdminPeers(eps)
|
globalAdminPeers = makeAdminPeers(endpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokeServiceCmd - Invoke Restart command.
|
// invokeServiceCmd - Invoke Restart command.
|
||||||
|
@ -18,7 +18,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/url"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -86,9 +85,7 @@ func TestReInitDisks(t *testing.T) {
|
|||||||
defer removeRoots(xlDirs)
|
defer removeRoots(xlDirs)
|
||||||
|
|
||||||
// Set globalEndpoints for a single node XL setup.
|
// Set globalEndpoints for a single node XL setup.
|
||||||
for _, xlDir := range xlDirs {
|
globalEndpoints = mustGetNewEndpointList(xlDirs...)
|
||||||
globalEndpoints = append(globalEndpoints, &url.URL{Path: xlDir})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup admin rpc server for an XL backend.
|
// Setup admin rpc server for an XL backend.
|
||||||
globalIsXL = true
|
globalIsXL = true
|
||||||
|
@ -16,7 +16,11 @@
|
|||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/pkg/set"
|
||||||
|
)
|
||||||
|
|
||||||
// List of valid event types.
|
// List of valid event types.
|
||||||
var suppportedEventTypes = map[string]struct{}{
|
var suppportedEventTypes = map[string]struct{}{
|
||||||
@ -207,16 +211,14 @@ func validateQueueConfigs(queueConfigs []queueConfig) APIErrorCode {
|
|||||||
|
|
||||||
// Check all the queue configs for any duplicates.
|
// Check all the queue configs for any duplicates.
|
||||||
func checkDuplicateQueueConfigs(configs []queueConfig) APIErrorCode {
|
func checkDuplicateQueueConfigs(configs []queueConfig) APIErrorCode {
|
||||||
var queueConfigARNS []string
|
queueConfigARNS := set.NewStringSet()
|
||||||
|
|
||||||
// Navigate through each configs and count the entries.
|
// Navigate through each configs and count the entries.
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
queueConfigARNS = append(queueConfigARNS, config.QueueARN)
|
queueConfigARNS.Add(config.QueueARN)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are any duplicate counts.
|
if len(queueConfigARNS) != len(configs) {
|
||||||
if err := checkDuplicateStrings(queueConfigARNS); err != nil {
|
|
||||||
errorIf(err, "Invalid queue configs found.")
|
|
||||||
return ErrOverlappingConfigs
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
objLayer, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
objLayer, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
removeRoots(disks)
|
removeRoots(disks)
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -94,6 +94,21 @@ type eventData struct {
|
|||||||
// New notification event constructs a new notification event message from
|
// New notification event constructs a new notification event message from
|
||||||
// input request metadata which completed successfully.
|
// input request metadata which completed successfully.
|
||||||
func newNotificationEvent(event eventData) NotificationEvent {
|
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.
|
// Fetch the region.
|
||||||
region := serverConfig.GetRegion()
|
region := serverConfig.GetRegion()
|
||||||
|
|
||||||
@ -103,14 +118,6 @@ func newNotificationEvent(event eventData) NotificationEvent {
|
|||||||
// Time when Minio finished processing the request.
|
// Time when Minio finished processing the request.
|
||||||
eventTime := UTCNow()
|
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.
|
// Fetch a hexadecimal representation of event time in nano seconds.
|
||||||
uniqueID := mustGetRequestID(eventTime)
|
uniqueID := mustGetRequestID(eventTime)
|
||||||
|
|
||||||
@ -131,7 +138,7 @@ func newNotificationEvent(event eventData) NotificationEvent {
|
|||||||
responseRequestIDKey: uniqueID,
|
responseRequestIDKey: uniqueID,
|
||||||
// Following is a custom response element to indicate
|
// Following is a custom response element to indicate
|
||||||
// event origin server endpoint.
|
// event origin server endpoint.
|
||||||
responseOriginEndpointKey: apiEndpoint,
|
responseOriginEndpointKey: getResponseOriginEndpointKey(),
|
||||||
},
|
},
|
||||||
S3: eventMeta{
|
S3: eventMeta{
|
||||||
SchemaVersion: eventSchemaVersion,
|
SchemaVersion: eventSchemaVersion,
|
||||||
|
@ -19,7 +19,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -40,11 +39,7 @@ func TestInitEventNotifierFaultyDisks(t *testing.T) {
|
|||||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||||
}
|
}
|
||||||
defer removeRoots(disks)
|
defer removeRoots(disks)
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to initialize FS backend.", err)
|
t.Fatal("Unable to initialize FS backend.", err)
|
||||||
}
|
}
|
||||||
@ -97,11 +92,7 @@ func TestInitEventNotifierWithPostgreSQL(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fs, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to initialize FS backend.", err)
|
t.Fatal("Unable to initialize FS backend.", err)
|
||||||
}
|
}
|
||||||
@ -128,11 +119,7 @@ func TestInitEventNotifierWithNATS(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fs, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to initialize FS backend.", err)
|
t.Fatal("Unable to initialize FS backend.", err)
|
||||||
}
|
}
|
||||||
@ -159,11 +146,7 @@ func TestInitEventNotifierWithWebHook(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fs, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to initialize FS backend.", err)
|
t.Fatal("Unable to initialize FS backend.", err)
|
||||||
}
|
}
|
||||||
@ -190,11 +173,7 @@ func TestInitEventNotifierWithAMQP(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fs, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to initialize FS backend.", err)
|
t.Fatal("Unable to initialize FS backend.", err)
|
||||||
}
|
}
|
||||||
@ -221,11 +200,7 @@ func TestInitEventNotifierWithElasticSearch(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fs, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to initialize FS backend.", err)
|
t.Fatal("Unable to initialize FS backend.", err)
|
||||||
}
|
}
|
||||||
@ -252,11 +227,7 @@ func TestInitEventNotifierWithRedis(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to create directories for FS backend. ", err)
|
t.Fatal("Unable to create directories for FS backend. ", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
fs, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fs, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to initialize FS backend.", err)
|
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)
|
s.testServer = StartTestPeersRPCServer(t, s.serverType)
|
||||||
|
|
||||||
// setup port and minio addr
|
// setup port and minio addr
|
||||||
host, port, err := net.SplitHostPort(s.testServer.Server.Listener.Addr().String())
|
host, port := mustSplitHostPort(s.testServer.Server.Listener.Addr().String())
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Initialisation error: %v", err)
|
|
||||||
}
|
|
||||||
globalMinioHost = host
|
globalMinioHost = host
|
||||||
globalMinioPort = port
|
globalMinioPort = port
|
||||||
globalMinioAddr = getLocalAddress(
|
globalMinioAddr = getEndpointsLocalAddr(s.testServer.endpoints)
|
||||||
s.testServer.SrvCmdCfg,
|
|
||||||
)
|
|
||||||
|
|
||||||
// initialize the peer client(s)
|
// initialize the peer client(s)
|
||||||
initGlobalS3Peers(s.testServer.Disks)
|
initGlobalS3Peers(s.testServer.Disks)
|
||||||
|
@ -273,12 +273,8 @@ func TestFormatXLHealFreshDisks(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create an instance of xl backend.
|
// Create an instance of xl backend.
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -309,12 +305,8 @@ func TestFormatXLHealFreshDisksErrorExpected(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create an instance of xl backend.
|
// Create an instance of xl backend.
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -596,12 +588,8 @@ func TestInitFormatXLErrors(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create an instance of xl backend.
|
// Create an instance of xl backend.
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -702,12 +690,8 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create an instance of xl backend.
|
// Create an instance of xl backend.
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -733,11 +717,7 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -761,11 +741,7 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -787,11 +763,7 @@ func TestLoadFormatXLErrs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -820,13 +792,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything is fine, should return nil
|
// Everything is fine, should return nil
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -842,13 +809,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disks 0..15 are nil
|
// Disks 0..15 are nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -866,13 +828,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk returns Faulty Disk
|
// One disk returns Faulty Disk
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -892,13 +849,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk is not found, heal corrupted disks should return nil
|
// One disk is not found, heal corrupted disks should return nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -914,13 +866,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove format.json of all disks
|
// Remove format.json of all disks
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -940,13 +887,8 @@ func TestHealFormatXLCorruptedDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Corrupted format json in one disk
|
// Corrupted format json in one disk
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -976,13 +918,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything is fine, should return nil
|
// Everything is fine, should return nil
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -997,13 +934,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disks 0..15 are nil
|
// Disks 0..15 are nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -1021,13 +953,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk returns Faulty Disk
|
// One disk returns Faulty Disk
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -1047,13 +974,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk is not found, heal corrupted disks should return nil
|
// One disk is not found, heal corrupted disks should return nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -1069,13 +991,8 @@ func TestHealFormatXLFreshDisksErrs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove format.json of all disks
|
// Remove format.json of all disks
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -187,9 +187,6 @@ func gatewayMain(ctx *cli.Context) {
|
|||||||
fatalIf(aerr, "Failed to start minio server")
|
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.
|
// Once endpoints are finalized, initialize the new object api.
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
globalObjectAPI = newObject
|
globalObjectAPI = newObject
|
||||||
@ -202,7 +199,8 @@ func gatewayMain(ctx *cli.Context) {
|
|||||||
mode = globalMinioModeGatewayAzure
|
mode = globalMinioModeGatewayAzure
|
||||||
}
|
}
|
||||||
checkUpdate(mode)
|
checkUpdate(mode)
|
||||||
printGatewayStartupMessage(apiEndPoints, accessKey, secretKey, backendType)
|
apiEndpoints := getAPIEndpoints(apiServer.Addr)
|
||||||
|
printGatewayStartupMessage(apiEndpoints, accessKey, secretKey, backendType)
|
||||||
}
|
}
|
||||||
|
|
||||||
<-globalServiceDoneCh
|
<-globalServiceDoneCh
|
||||||
|
@ -18,7 +18,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net/url"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -85,9 +84,6 @@ var (
|
|||||||
// Holds the host that was passed using --address
|
// Holds the host that was passed using --address
|
||||||
globalMinioHost = ""
|
globalMinioHost = ""
|
||||||
|
|
||||||
// Holds the list of API endpoints for a given server.
|
|
||||||
globalAPIEndpoints = []string{}
|
|
||||||
|
|
||||||
// Peer communication struct
|
// Peer communication struct
|
||||||
globalS3Peers = s3Peers{}
|
globalS3Peers = s3Peers{}
|
||||||
|
|
||||||
@ -103,8 +99,7 @@ var (
|
|||||||
// Minio server user agent string.
|
// Minio server user agent string.
|
||||||
globalServerUserAgent = "Minio/" + ReleaseTag + " (" + runtime.GOOS + "; " + runtime.GOARCH + ")"
|
globalServerUserAgent = "Minio/" + ReleaseTag + " (" + runtime.GOOS + "; " + runtime.GOARCH + ")"
|
||||||
|
|
||||||
// url.URL endpoints of disks that belong to the object storage.
|
globalEndpoints EndpointList
|
||||||
globalEndpoints = []*url.URL{}
|
|
||||||
|
|
||||||
// Global server's network statistics
|
// Global server's network statistics
|
||||||
globalConnStats = newConnStats()
|
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.
|
// 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.
|
// Initialize a new set of lock servers.
|
||||||
lockServers := newLockServers(serverConfig)
|
lockServers := newLockServers(endpoints)
|
||||||
|
|
||||||
// Start lock maintenance from all lock servers.
|
// Start lock maintenance from all lock servers.
|
||||||
startLockMaintainence(lockServers)
|
startLockMaintainence(lockServers)
|
||||||
@ -102,19 +102,18 @@ func registerDistNSLockRouter(mux *router.Router, serverConfig serverCmdConfig)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create one lock server for every local storage rpc server.
|
// Create one lock server for every local storage rpc server.
|
||||||
func newLockServers(srvConfig serverCmdConfig) (lockServers []*lockServer) {
|
func newLockServers(endpoints EndpointList) (lockServers []*lockServer) {
|
||||||
for _, ep := range srvConfig.endpoints {
|
for _, endpoint := range endpoints {
|
||||||
// Initialize new lock server for each local node.
|
// Initialize new lock server for each local node.
|
||||||
if isLocalStorage(ep) {
|
if endpoint.IsLocal {
|
||||||
// Create handler for lock RPCs
|
lockServers = append(lockServers, &lockServer{
|
||||||
locker := &lockServer{
|
serviceEndpoint: endpoint.Path,
|
||||||
serviceEndpoint: getPath(ep),
|
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
lockMap: make(map[string][]lockRequesterInfo),
|
lockMap: make(map[string][]lockRequesterInfo),
|
||||||
}
|
})
|
||||||
lockServers = append(lockServers, locker)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lockServers
|
return lockServers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@ -433,66 +432,46 @@ func TestLockServers(t *testing.T) {
|
|||||||
globalIsDistXL = currentIsDistXL
|
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 = ""
|
globalMinioHost = ""
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
isDistXL bool
|
isDistXL bool
|
||||||
srvCmdConfig serverCmdConfig
|
endpoints EndpointList
|
||||||
totalLockServers int
|
totalLockServers int
|
||||||
}{
|
}{
|
||||||
// Test - 1 one lock server initialized.
|
// Test - 1 one lock server initialized.
|
||||||
{
|
{true, case1Endpoints, 1},
|
||||||
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,
|
|
||||||
},
|
|
||||||
// Test - 2 two servers possible.
|
// Test - 2 two servers possible.
|
||||||
{
|
{true, case2Endpoints, 2},
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates lock server initialization.
|
// Validates lock server initialization.
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
globalIsDistXL = testCase.isDistXL
|
globalIsDistXL = testCase.isDistXL
|
||||||
lockServers := newLockServers(testCase.srvCmdConfig)
|
lockServers := newLockServers(testCase.endpoints)
|
||||||
if len(lockServers) != testCase.totalLockServers {
|
if len(lockServers) != testCase.totalLockServers {
|
||||||
t.Fatalf("Test %d: Expected total %d, got %d", i+1, testCase.totalLockServers, len(lockServers))
|
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.
|
// Initialize rpc lock client information only if this instance is a distributed setup.
|
||||||
clnts := make([]dsync.NetLocker, len(globalEndpoints))
|
clnts := make([]dsync.NetLocker, len(globalEndpoints))
|
||||||
myNode := -1
|
myNode := -1
|
||||||
for index, ep := range globalEndpoints {
|
for index, endpoint := range globalEndpoints {
|
||||||
if ep == nil {
|
|
||||||
return errInvalidArgument
|
|
||||||
}
|
|
||||||
clnts[index] = newLockRPCClient(authConfig{
|
clnts[index] = newLockRPCClient(authConfig{
|
||||||
accessKey: cred.AccessKey,
|
accessKey: cred.AccessKey,
|
||||||
secretKey: cred.SecretKey,
|
secretKey: cred.SecretKey,
|
||||||
serverAddr: ep.Host,
|
serverAddr: endpoint.Host,
|
||||||
secureConn: globalIsSSL,
|
secureConn: globalIsSSL,
|
||||||
serviceEndpoint: pathutil.Join(minioReservedBucketPath, lockServicePath, getPath(ep)),
|
serviceEndpoint: pathutil.Join(minioReservedBucketPath, lockServicePath, endpoint.Path),
|
||||||
serviceName: lockServiceName,
|
serviceName: lockServiceName,
|
||||||
})
|
})
|
||||||
if isLocalStorage(ep) && myNode == -1 {
|
if endpoint.IsLocal && myNode == -1 {
|
||||||
myNode = index
|
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
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
@ -132,90 +129,13 @@ func houseKeeping(storageDisks []StorageAPI) error {
|
|||||||
return nil
|
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.
|
// Depending on the disk type network or local, initialize storage API.
|
||||||
func newStorageAPI(ep *url.URL) (storage StorageAPI, err error) {
|
func newStorageAPI(endpoint Endpoint) (storage StorageAPI, err error) {
|
||||||
if isLocalStorage(ep) {
|
if endpoint.IsLocal {
|
||||||
return newPosix(getPath(ep))
|
return newPosix(endpoint.Path)
|
||||||
}
|
}
|
||||||
return newStorageRPC(ep)
|
|
||||||
|
return newStorageRPC(endpoint), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var initMetaVolIgnoredErrs = append(baseIgnoredErrs, errVolumeExists)
|
var initMetaVolIgnoredErrs = append(baseIgnoredErrs, errVolumeExists)
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
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.
|
// 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)
|
msg := getHealMsg(endpoints, storageDisks)
|
||||||
fn(msg)
|
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..
|
// Disks offline and online strings..
|
||||||
const (
|
const (
|
||||||
diskOffline = "offline"
|
diskOffline = "offline"
|
||||||
@ -100,7 +64,7 @@ const (
|
|||||||
// Constructs a formatted heal message, when cluster is found to be in state where it requires healing.
|
// 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.
|
// 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.
|
// 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"`
|
healFmtCmd := `"mc admin heal myminio"`
|
||||||
msg := fmt.Sprintf("New disk(s) were found, format them by running - %s\n",
|
msg := fmt.Sprintf("New disk(s) were found, format them by running - %s\n",
|
||||||
healFmtCmd)
|
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.
|
// 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)
|
msg := getStorageInitMsg("\nInitializing data volume.", endpoints, storageDisks)
|
||||||
fn(msg)
|
fn(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructs a formatted regular message when we have sufficient disks to start the cluster.
|
// 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)
|
msg := colorBlue(titleMsg)
|
||||||
disksInfo, _, _ := getDisksInfo(storageDisks)
|
disksInfo, _, _ := getDisksInfo(storageDisks)
|
||||||
for i, info := range disksInfo {
|
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.
|
// 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)
|
msg := getStorageInitMsg("\nInitializing data volume for the first time.", endpoints, storageDisks)
|
||||||
fn(msg)
|
fn(msg)
|
||||||
}
|
}
|
||||||
|
@ -17,60 +17,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"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.
|
// Tests heal message to be correct and properly formatted.
|
||||||
func TestHealMsg(t *testing.T) {
|
func TestHealMsg(t *testing.T) {
|
||||||
rootPath, err := newTestConfig(globalMinioDefaultRegion)
|
rootPath, err := newTestConfig(globalMinioDefaultRegion)
|
||||||
@ -85,39 +35,26 @@ func TestHealMsg(t *testing.T) {
|
|||||||
nilDisks[5] = nil
|
nilDisks[5] = nil
|
||||||
authErrs := make([]error, len(storageDisks))
|
authErrs := make([]error, len(storageDisks))
|
||||||
authErrs[5] = errAuthentication
|
authErrs[5] = errAuthentication
|
||||||
endpointURL, err := url.Parse("http://10.1.10.1:9000")
|
|
||||||
if err != nil {
|
args := []string{}
|
||||||
t.Fatal("Unexpected error:", err)
|
for i := range storageDisks {
|
||||||
}
|
args = append(args, fmt.Sprintf("http://10.1.10.%d:9000/d1", i+1))
|
||||||
endpointURLs := make([]*url.URL, len(storageDisks))
|
|
||||||
for idx := 0; idx < len(endpointURLs); idx++ {
|
|
||||||
endpointURLs[idx] = endpointURL
|
|
||||||
}
|
}
|
||||||
|
endpoints := mustGetNewEndpointList(args...)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
endPoints []*url.URL
|
endPoints EndpointList
|
||||||
storageDisks []StorageAPI
|
storageDisks []StorageAPI
|
||||||
serrs []error
|
serrs []error
|
||||||
}{
|
}{
|
||||||
// Test - 1 for valid disks and errors.
|
// Test - 1 for valid disks and errors.
|
||||||
{
|
{endpoints, storageDisks, errs},
|
||||||
endPoints: endpointURLs,
|
|
||||||
storageDisks: storageDisks,
|
|
||||||
serrs: errs,
|
|
||||||
},
|
|
||||||
// Test - 2 for one of the disks is nil.
|
// Test - 2 for one of the disks is nil.
|
||||||
{
|
{endpoints, nilDisks, errs},
|
||||||
endPoints: endpointURLs,
|
|
||||||
storageDisks: nilDisks,
|
|
||||||
serrs: errs,
|
|
||||||
},
|
|
||||||
// Test - 3 for one of the errs is authentication.
|
// Test - 3 for one of the errs is authentication.
|
||||||
{
|
{endpoints, nilDisks, authErrs},
|
||||||
endPoints: endpointURLs,
|
|
||||||
storageDisks: nilDisks,
|
|
||||||
serrs: authErrs,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
msg := getHealMsg(testCase.endPoints, testCase.storageDisks)
|
msg := getHealMsg(testCase.endPoints, testCase.storageDisks)
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
|
@ -18,7 +18,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/mc/pkg/console"
|
"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
|
// Implements a jitter backoff loop for formatting all disks during
|
||||||
// initialization of the server.
|
// 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 {
|
if len(endpoints) == 0 {
|
||||||
return errInvalidArgument
|
return errInvalidArgument
|
||||||
}
|
}
|
||||||
@ -276,16 +275,13 @@ func retryFormattingXLDisks(firstDisk bool, endpoints []*url.URL, storageDisks [
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize storage disks based on input arguments.
|
// Initialize storage disks based on input arguments.
|
||||||
func initStorageDisks(endpoints []*url.URL) ([]StorageAPI, error) {
|
func initStorageDisks(endpoints EndpointList) ([]StorageAPI, error) {
|
||||||
// Bootstrap disks.
|
// Bootstrap disks.
|
||||||
storageDisks := make([]StorageAPI, len(endpoints))
|
storageDisks := make([]StorageAPI, len(endpoints))
|
||||||
for index, ep := range endpoints {
|
for index, endpoint := range endpoints {
|
||||||
if ep == nil {
|
|
||||||
return nil, errInvalidArgument
|
|
||||||
}
|
|
||||||
// Intentionally ignore disk not found errors. XL is designed
|
// Intentionally ignore disk not found errors. XL is designed
|
||||||
// to handle these errors internally.
|
// to handle these errors internally.
|
||||||
storage, err := newStorageAPI(ep)
|
storage, err := newStorageAPI(endpoint)
|
||||||
if err != nil && err != errDiskNotFound {
|
if err != nil && err != errDiskNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -295,14 +291,10 @@ func initStorageDisks(endpoints []*url.URL) ([]StorageAPI, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Format disks before initialization of object layer.
|
// 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 {
|
if len(endpoints) == 0 {
|
||||||
return nil, errInvalidArgument
|
return nil, errInvalidArgument
|
||||||
}
|
}
|
||||||
firstEndpoint := endpoints[0]
|
|
||||||
if firstEndpoint == nil {
|
|
||||||
return nil, errInvalidArgument
|
|
||||||
}
|
|
||||||
if storageDisks == nil {
|
if storageDisks == nil {
|
||||||
return nil, errInvalidArgument
|
return nil, errInvalidArgument
|
||||||
}
|
}
|
||||||
|
@ -30,15 +30,15 @@ func newObjectLayerFn() (layer ObjectLayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Composed function registering routers for only distributed XL setup.
|
// 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.
|
// Register storage rpc router only if its a distributed setup.
|
||||||
err := registerStorageRPCRouters(mux, srvCmdConfig)
|
err := registerStorageRPCRouters(mux, endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register distributed namespace lock.
|
// Register distributed namespace lock.
|
||||||
err = registerDistNSLockRouter(mux, srvCmdConfig)
|
err = registerDistNSLockRouter(mux, endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -54,14 +54,14 @@ func registerDistXLRouters(mux *router.Router, srvCmdConfig serverCmdConfig) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// configureServer handler returns final handler for the http server.
|
// 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
|
// Initialize router. `SkipClean(true)` stops gorilla/mux from
|
||||||
// normalizing URL path minio/minio#3256
|
// normalizing URL path minio/minio#3256
|
||||||
mux := router.NewRouter().SkipClean(true)
|
mux := router.NewRouter().SkipClean(true)
|
||||||
|
|
||||||
// Initialize distributed NS lock.
|
// Initialize distributed NS lock.
|
||||||
if globalIsDistXL {
|
if globalIsDistXL {
|
||||||
registerDistXLRouters(mux, srvCmdConfig)
|
registerDistXLRouters(mux, endpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Admin RPC router
|
// Add Admin RPC router
|
||||||
|
@ -19,9 +19,11 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
// s3Peer structs contains the address of a peer in the cluster, and
|
// 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
|
// makeS3Peers makes an s3Peers struct value from the given urls
|
||||||
// slice. The urls slice is assumed to be non-empty and free of nil
|
// slice. The urls slice is assumed to be non-empty and free of nil
|
||||||
// values.
|
// values.
|
||||||
func makeS3Peers(eps []*url.URL) s3Peers {
|
func makeS3Peers(endpoints EndpointList) (s3PeerList s3Peers) {
|
||||||
var ret []s3Peer
|
thisPeer := globalMinioAddr
|
||||||
|
if globalMinioHost == "" {
|
||||||
// map to store peers that are already added to ret
|
thisPeer = net.JoinHostPort("localhost", globalMinioPort)
|
||||||
seenAddr := make(map[string]bool)
|
}
|
||||||
|
s3PeerList = append(s3PeerList, s3Peer{
|
||||||
// add local (self) as peer in the array
|
thisPeer,
|
||||||
ret = append(ret, s3Peer{
|
|
||||||
globalMinioAddr,
|
|
||||||
&localBucketMetaState{ObjectAPI: newObjectLayerFn},
|
&localBucketMetaState{ObjectAPI: newObjectLayerFn},
|
||||||
})
|
})
|
||||||
seenAddr[globalMinioAddr] = true
|
|
||||||
|
|
||||||
serverCred := serverConfig.GetCredential()
|
hostSet := set.CreateStringSet(globalMinioAddr)
|
||||||
// iterate over endpoints to find new remote peers and add
|
cred := serverConfig.GetCredential()
|
||||||
// them to ret.
|
serviceEndpoint := path.Join(minioReservedBucketPath, s3Path)
|
||||||
for _, ep := range eps {
|
for _, host := range GetRemotePeers(endpoints) {
|
||||||
if ep.Host == "" {
|
if hostSet.Contains(host) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
hostSet.Add(host)
|
||||||
// Check if the remote host has been added already
|
s3PeerList = append(s3PeerList, s3Peer{
|
||||||
if !seenAddr[ep.Host] {
|
addr: host,
|
||||||
cfg := authConfig{
|
bmsClient: &remoteBucketMetaState{newAuthRPCClient(authConfig{
|
||||||
accessKey: serverCred.AccessKey,
|
accessKey: cred.AccessKey,
|
||||||
secretKey: serverCred.SecretKey,
|
secretKey: cred.SecretKey,
|
||||||
serverAddr: ep.Host,
|
serverAddr: host,
|
||||||
serviceEndpoint: path.Join(minioReservedBucketPath, s3Path),
|
serviceEndpoint: serviceEndpoint,
|
||||||
secureConn: globalIsSSL,
|
secureConn: globalIsSSL,
|
||||||
serviceName: "S3",
|
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
|
// initGlobalS3Peers - initialize globalS3Peers by passing in
|
||||||
// endpoints - intended to be called early in program start-up.
|
// endpoints - intended to be called early in program start-up.
|
||||||
func initGlobalS3Peers(eps []*url.URL) {
|
func initGlobalS3Peers(endpoints EndpointList) {
|
||||||
globalS3Peers = makeS3Peers(eps)
|
globalS3Peers = makeS3Peers(endpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerClient - fetch BucketMetaState interface by peer address
|
// GetPeerClient - fetch BucketMetaState interface by peer address
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -35,12 +34,12 @@ func TestMakeS3Peers(t *testing.T) {
|
|||||||
// test cases
|
// test cases
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
gMinioAddr string
|
gMinioAddr string
|
||||||
eps []*url.URL
|
eps EndpointList
|
||||||
peers []string
|
peers []string
|
||||||
}{
|
}{
|
||||||
{":9000", []*url.URL{{Path: "/mnt/disk1"}}, []string{":9000"}},
|
{"127.0.0.1:9000", mustGetNewEndpointList("/mnt/disk1"), []string{"127.0.0.1:9000"}},
|
||||||
{":9000", []*url.URL{{Host: "localhost:9001"}}, []string{":9000", "localhost:9001"}},
|
{"127.0.0.1:9000", mustGetNewEndpointList("http://localhost:9001/d1"), []string{"127.0.0.1:9000", "localhost:9001"}},
|
||||||
{"m1:9000", []*url.URL{{Host: "m1:9000"}, {Host: "m2:9000"}, {Host: "m3:9000"}}, []string{"m1:9000", "m2:9000", "m3:9000"}},
|
{"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 {
|
getPeersHelper := func(s3p s3Peers) []string {
|
||||||
|
@ -18,19 +18,12 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/minio/cli"
|
"github.com/minio/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -110,249 +103,6 @@ func enableLoggers() {
|
|||||||
log.SetConsoleTarget(consoleLogTarget)
|
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() {
|
func initConfig() {
|
||||||
// Config file does not exist, we create it fresh and return upon success.
|
// Config file does not exist, we create it fresh and return upon success.
|
||||||
if isFile(getConfigFile()) {
|
if isFile(getConfigFile()) {
|
||||||
@ -365,6 +115,8 @@ func initConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func serverHandleCmdArgs(ctx *cli.Context) {
|
func serverHandleCmdArgs(ctx *cli.Context) {
|
||||||
|
// Set configuration directory.
|
||||||
|
{
|
||||||
// Get configuration directory from command line argument.
|
// Get configuration directory from command line argument.
|
||||||
configDir := ctx.String("config-dir")
|
configDir := ctx.String("config-dir")
|
||||||
if !ctx.IsSet("config-dir") && ctx.GlobalIsSet("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.
|
// Disallow relative paths, figure out absolute paths.
|
||||||
{
|
|
||||||
configDirAbs, err := filepath.Abs(configDir)
|
configDirAbs, err := filepath.Abs(configDir)
|
||||||
fatalIf(err, "Unable to fetch absolute path for config directory %s", configDir)
|
fatalIf(err, "Unable to fetch absolute path for config directory %s", configDir)
|
||||||
|
|
||||||
configDir = configDirAbs
|
setConfigDir(configDirAbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set configuration directory.
|
|
||||||
setConfigDir(configDir)
|
|
||||||
|
|
||||||
// Server address.
|
// 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
|
var err error
|
||||||
globalMinioHost, globalMinioPort, err = getHostPort(globalMinioAddr)
|
globalMinioAddr, globalEndpoints, setupType, err = CreateEndpoints(serverAddr, ctx.Args()...)
|
||||||
fatalIf(err, "Unable to extract host and port %s", globalMinioAddr)
|
fatalIf(err, "Invalid command line arguments server=‘%s’, args=%s", serverAddr, ctx.Args())
|
||||||
|
globalMinioHost, globalMinioPort = mustSplitHostPort(globalMinioAddr)
|
||||||
// Disks to be used in server init.
|
if runtime.GOOS == "darwin" {
|
||||||
endpoints, err := parseStorageEndpoints(ctx.Args())
|
// On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back
|
||||||
fatalIf(err, "Unable to parse storage endpoints %s", ctx.Args())
|
// to IPv6 address ie minio will start listening on IPv6 address whereas another
|
||||||
|
// (non-)minio process is listening on IPv4 of given port.
|
||||||
// Sort endpoints for consistent ordering across multiple
|
// To avoid this error sutiation we check for port availability only for macOS.
|
||||||
// nodes in a distributed setup. This is to avoid format.json
|
fatalIf(checkPortAvailability(globalMinioPort), "Port %d already in use", globalMinioPort)
|
||||||
// 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.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if endpoints are part of distributed setup.
|
globalIsXL = (setupType == XLSetupType)
|
||||||
globalIsDistXL = isDistributedSetup(endpoints)
|
globalIsDistXL = (setupType == DistXLSetupType)
|
||||||
|
if globalIsDistXL {
|
||||||
// Set globalIsXL if erasure code backend is about to be
|
|
||||||
// initialized for the given endpoints.
|
|
||||||
if len(endpoints) > 1 {
|
|
||||||
globalIsXL = true
|
globalIsXL = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set endpoints of []*url.URL type to globalEndpoints.
|
|
||||||
globalEndpoints = endpoints
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func serverHandleEnvVars() {
|
func serverHandleEnvVars() {
|
||||||
@ -497,11 +231,10 @@ func serverMain(ctx *cli.Context) {
|
|||||||
if !quietFlag {
|
if !quietFlag {
|
||||||
// Check for new updates from dl.minio.io.
|
// Check for new updates from dl.minio.io.
|
||||||
mode := globalMinioModeFS
|
mode := globalMinioModeFS
|
||||||
if globalIsXL {
|
|
||||||
mode = globalMinioModeXL
|
|
||||||
}
|
|
||||||
if globalIsDistXL {
|
if globalIsDistXL {
|
||||||
mode = globalMinioModeDistXL
|
mode = globalMinioModeDistXL
|
||||||
|
} else if globalIsXL {
|
||||||
|
mode = globalMinioModeXL
|
||||||
}
|
}
|
||||||
checkUpdate(mode)
|
checkUpdate(mode)
|
||||||
}
|
}
|
||||||
@ -518,31 +251,18 @@ func serverMain(ctx *cli.Context) {
|
|||||||
initNSLock(globalIsDistXL)
|
initNSLock(globalIsDistXL)
|
||||||
|
|
||||||
// Configure server.
|
// Configure server.
|
||||||
srvConfig := serverCmdConfig{
|
handler, err := configureServerHandler(globalEndpoints)
|
||||||
serverAddr: globalMinioAddr,
|
|
||||||
endpoints: globalEndpoints,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure server.
|
|
||||||
handler, err := configureServerHandler(srvConfig)
|
|
||||||
fatalIf(err, "Unable to configure one of server's RPC services.")
|
fatalIf(err, "Unable to configure one of server's RPC services.")
|
||||||
|
|
||||||
// Initialize a new HTTP server.
|
// Initialize a new HTTP server.
|
||||||
apiServer := NewServerMux(globalMinioAddr, handler)
|
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.
|
// Initialize S3 Peers inter-node communication only in distributed setup.
|
||||||
initGlobalS3Peers(globalEndpoints)
|
initGlobalS3Peers(globalEndpoints)
|
||||||
|
|
||||||
// Initialize Admin Peers inter-node communication only in distributed setup.
|
// Initialize Admin Peers inter-node communication only in distributed setup.
|
||||||
initGlobalAdminPeers(globalEndpoints)
|
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.
|
// Start server, automatically configures TLS if certs are available.
|
||||||
go func() {
|
go func() {
|
||||||
cert, key := "", ""
|
cert, key := "", ""
|
||||||
@ -552,7 +272,7 @@ func serverMain(ctx *cli.Context) {
|
|||||||
fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server.")
|
fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server.")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
newObject, err := newObjectLayer(srvConfig)
|
newObject, err := newObjectLayer(globalEndpoints)
|
||||||
fatalIf(err, "Initializing object layer failed")
|
fatalIf(err, "Initializing object layer failed")
|
||||||
|
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
@ -560,7 +280,8 @@ func serverMain(ctx *cli.Context) {
|
|||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
|
|
||||||
// Prints the formatted startup message once object layer is initialized.
|
// 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.
|
// Set uptime time after object layer has initialized.
|
||||||
globalBootTime = UTCNow()
|
globalBootTime = UTCNow()
|
||||||
@ -570,40 +291,26 @@ func serverMain(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize object layer with the supplied disks, objectLayer is nil upon any error.
|
// 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.
|
// For FS only, directly use the disk.
|
||||||
isFS := len(srvCmdCfg.endpoints) == 1
|
isFS := len(endpoints) == 1
|
||||||
if isFS {
|
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.
|
// Initialize new FS object layer.
|
||||||
newObject, err = newFSObjectLayer(fsPath)
|
return newFSObjectLayer(endpoints[0].Path)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FS initialized, return.
|
|
||||||
return newObject, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// First disk argument check if it is local.
|
|
||||||
firstDisk := isLocalStorage(srvCmdCfg.endpoints[0])
|
|
||||||
|
|
||||||
// Initialize storage disks.
|
// Initialize storage disks.
|
||||||
storageDisks, err := initStorageDisks(srvCmdCfg.endpoints)
|
storageDisks, err := initStorageDisks(endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for formatting disks for XL backend.
|
// Wait for formatting disks for XL backend.
|
||||||
var formattedDisks []StorageAPI
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -17,205 +17,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"testing"
|
"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.
|
// Tests initializing new object layer.
|
||||||
func TestNewObjectLayer(t *testing.T) {
|
func TestNewObjectLayer(t *testing.T) {
|
||||||
// Tests for FS object layer.
|
// Tests for FS object layer.
|
||||||
@ -226,15 +31,8 @@ func TestNewObjectLayer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(disks)
|
defer removeRoots(disks)
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
endpoints := mustGetNewEndpointList(disks...)
|
||||||
if err != nil {
|
obj, err := newObjectLayer(endpoints)
|
||||||
t.Fatal("Unexpected parse error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := newObjectLayer(serverCmdConfig{
|
|
||||||
serverAddr: ":9000",
|
|
||||||
endpoints: endpoints,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unexpected object layer initialization error", err)
|
t.Fatal("Unexpected object layer initialization error", err)
|
||||||
}
|
}
|
||||||
@ -253,15 +51,8 @@ func TestNewObjectLayer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(disks)
|
defer removeRoots(disks)
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(disks)
|
endpoints = mustGetNewEndpointList(disks...)
|
||||||
if err != nil {
|
obj, err = newObjectLayer(endpoints)
|
||||||
t.Fatal("Unexpected parse error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err = newObjectLayer(serverCmdConfig{
|
|
||||||
serverAddr: ":9000",
|
|
||||||
endpoints: endpoints,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unexpected object layer initialization error", err)
|
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))
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,19 +16,29 @@
|
|||||||
|
|
||||||
package cmd
|
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) {
|
// XLSetupType - XL setup type enum.
|
||||||
s[i], s[j] = s[j], s[i]
|
XLSetupType
|
||||||
}
|
|
||||||
|
// DistXLSetupType - Distributed XL setup type enum.
|
||||||
func (s byHostPath) Len() int {
|
DistXLSetupType
|
||||||
return len(s)
|
)
|
||||||
}
|
|
||||||
|
func (setupType SetupType) String() string {
|
||||||
// Note: Host in url.URL includes the port too.
|
switch setupType {
|
||||||
func (s byHostPath) Less(i, j int) bool {
|
case FSSetupType:
|
||||||
return (s[i].Host + s[i].Path) < (s[j].Host + s[j].Path)
|
return globalMinioModeFS
|
||||||
|
case XLSetupType:
|
||||||
|
return globalMinioModeXL
|
||||||
|
case DistXLSetupType:
|
||||||
|
return globalMinioModeDistXL
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
}
|
}
|
@ -85,8 +85,12 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
|
|||||||
cred := serverConfig.GetCredential()
|
cred := serverConfig.GetCredential()
|
||||||
|
|
||||||
// r.RequestURI will have raw encoded URI as sent by the client.
|
// r.RequestURI will have raw encoded URI as sent by the client.
|
||||||
splits := splitStr(r.RequestURI, "?", 2)
|
tokens := strings.SplitN(r.RequestURI, "?", 2)
|
||||||
encodedResource, encodedQuery := splits[0], splits[1]
|
encodedResource := tokens[0]
|
||||||
|
encodedQuery := ""
|
||||||
|
if len(tokens) == 2 {
|
||||||
|
encodedQuery = tokens[1]
|
||||||
|
}
|
||||||
|
|
||||||
queries := strings.Split(encodedQuery, "&")
|
queries := strings.Split(encodedQuery, "&")
|
||||||
var filteredQueries []string
|
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.
|
// r.RequestURI will have raw encoded URI as sent by the client.
|
||||||
splits := splitStr(r.RequestURI, "?", 2)
|
tokens := strings.SplitN(r.RequestURI, "?", 2)
|
||||||
encodedResource, encodedQuery := splits[0], splits[1]
|
encodedResource := tokens[0]
|
||||||
|
encodedQuery := ""
|
||||||
|
if len(tokens) == 2 {
|
||||||
|
encodedQuery = tokens[1]
|
||||||
|
}
|
||||||
|
|
||||||
expectedAuth := signatureV2(r.Method, encodedResource, encodedQuery, r.Header)
|
expectedAuth := signatureV2(r.Method, encodedResource, encodedQuery, r.Header)
|
||||||
if v2Auth != expectedAuth {
|
if v2Auth != expectedAuth {
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
"net/url"
|
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -96,39 +95,22 @@ func toStorageErr(err error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize new storage rpc client.
|
// Initialize new storage rpc client.
|
||||||
func newStorageRPC(ep *url.URL) (StorageAPI, error) {
|
func newStorageRPC(endpoint Endpoint) StorageAPI {
|
||||||
if ep == nil {
|
|
||||||
return nil, errInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial minio rpc storage http path.
|
// Dial minio rpc storage http path.
|
||||||
rpcPath := path.Join(minioReservedBucketPath, storageRPCPath, getPath(ep))
|
rpcPath := path.Join(minioReservedBucketPath, storageRPCPath, endpoint.Path)
|
||||||
rpcAddr := ep.Host
|
|
||||||
|
|
||||||
serverCred := serverConfig.GetCredential()
|
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{
|
rpcClient: newAuthRPCClient(authConfig{
|
||||||
accessKey: accessKey,
|
accessKey: serverCred.AccessKey,
|
||||||
secretKey: secretKey,
|
secretKey: serverCred.SecretKey,
|
||||||
serverAddr: rpcAddr,
|
serverAddr: endpoint.Host,
|
||||||
serviceEndpoint: rpcPath,
|
serviceEndpoint: rpcPath,
|
||||||
secureConn: globalIsSSL,
|
secureConn: globalIsSSL,
|
||||||
serviceName: "Storage",
|
serviceName: "Storage",
|
||||||
disableReconnect: true,
|
disableReconnect: true,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns successfully here.
|
|
||||||
return storageAPI, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stringer interface compatible representation of network device.
|
// Stringer interface compatible representation of network device.
|
||||||
|
@ -23,7 +23,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
"net/url"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -146,25 +145,15 @@ func (s *TestRPCStorageSuite) SetUpSuite(c *testing.T) {
|
|||||||
listenAddress := s.testServer.Server.Listener.Addr().String()
|
listenAddress := s.testServer.Server.Listener.Addr().String()
|
||||||
|
|
||||||
for _, ep := range s.testServer.Disks {
|
for _, ep := range s.testServer.Disks {
|
||||||
ep.Host = listenAddress
|
// Eventhough s.testServer.Disks is EndpointList, we would need a URLEndpointType here.
|
||||||
storageDisk, err := newStorageRPC(ep)
|
endpoint := ep
|
||||||
if err != nil {
|
if endpoint.Type() == PathEndpointType {
|
||||||
c.Fatal("Unable to initialize RPC client", err)
|
endpoint.Scheme = "http"
|
||||||
}
|
}
|
||||||
|
endpoint.Host = listenAddress
|
||||||
|
storageDisk := newStorageRPC(endpoint)
|
||||||
s.remoteDisks = append(s.remoteDisks, storageDisk)
|
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
|
// 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.
|
// Initialize new storage rpc.
|
||||||
func newRPCServer(srvConfig serverCmdConfig) (servers []*storageServer, err error) {
|
func newRPCServer(endpoints EndpointList) (servers []*storageServer, err error) {
|
||||||
for _, ep := range srvConfig.endpoints {
|
for _, endpoint := range endpoints {
|
||||||
// e.g server:/mnt/disk1
|
if endpoint.IsLocal {
|
||||||
if isLocalStorage(ep) {
|
storage, err := newPosix(endpoint.Path)
|
||||||
// Get the posix path.
|
|
||||||
path := getPath(ep)
|
|
||||||
var storage StorageAPI
|
|
||||||
storage, err = newPosix(path)
|
|
||||||
if err != nil && err != errDiskNotFound {
|
if err != nil && err != errDiskNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
servers = append(servers, &storageServer{
|
servers = append(servers, &storageServer{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
path: path,
|
path: endpoint.Path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers, nil
|
return servers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerStorageRPCRouter - register storage rpc router.
|
// 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.
|
// Initialize storage rpc servers for every disk that is hosted on this node.
|
||||||
storageRPCs, err := newRPCServer(srvCmdConfig)
|
storageRPCs, err := newRPCServer(endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return traceError(err)
|
return traceError(err)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/disk"
|
"github.com/minio/minio/pkg/disk"
|
||||||
@ -30,7 +29,7 @@ type testStorageRPCServer struct {
|
|||||||
token string
|
token string
|
||||||
diskDirs []string
|
diskDirs []string
|
||||||
stServer *storageServer
|
stServer *storageServer
|
||||||
endpoints []*url.URL
|
endpoints EndpointList
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestStorageServer(t *testing.T) *testStorageRPCServer {
|
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)
|
t.Fatalf("unable to create FS backend, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
endpoints := mustGetNewEndpointList(fsDirs...)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to parse storage endpoints, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
storageDisks, err := initStorageDisks(endpoints)
|
storageDisks, err := initStorageDisks(endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initialize storage disks, %s", err)
|
t.Fatalf("unable to initialize storage disks, %s", err)
|
||||||
|
@ -91,11 +91,7 @@ func prepareXL() (ObjectLayer, []string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
removeRoots(fsDirs)
|
removeRoots(fsDirs)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@ -180,12 +176,12 @@ func isSameType(obj1, obj2 interface{}) bool {
|
|||||||
// defer s.Stop()
|
// defer s.Stop()
|
||||||
type TestServer struct {
|
type TestServer struct {
|
||||||
Root string
|
Root string
|
||||||
Disks []*url.URL
|
Disks EndpointList
|
||||||
AccessKey string
|
AccessKey string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
Server *httptest.Server
|
Server *httptest.Server
|
||||||
Obj ObjectLayer
|
Obj ObjectLayer
|
||||||
SrvCmdCfg serverCmdConfig
|
endpoints EndpointList
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnstartedTestServer - Configures a temp FS/XL backend,
|
// UnstartedTestServer - Configures a temp FS/XL backend,
|
||||||
@ -210,50 +206,31 @@ func UnstartedTestServer(t TestErrHandler, instanceType string) TestServer {
|
|||||||
credentials := serverConfig.GetCredential()
|
credentials := serverConfig.GetCredential()
|
||||||
|
|
||||||
testServer.Obj = objLayer
|
testServer.Obj = objLayer
|
||||||
testServer.Disks, err = parseStorageEndpoints(disks)
|
testServer.Disks = mustGetNewEndpointList(disks...)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error %v", err)
|
|
||||||
}
|
|
||||||
testServer.Root = root
|
testServer.Root = root
|
||||||
testServer.AccessKey = credentials.AccessKey
|
testServer.AccessKey = credentials.AccessKey
|
||||||
testServer.SecretKey = credentials.SecretKey
|
testServer.SecretKey = credentials.SecretKey
|
||||||
|
|
||||||
srvCmdCfg := serverCmdConfig{
|
httpHandler, err := configureServerHandler(testServer.Disks)
|
||||||
endpoints: testServer.Disks,
|
|
||||||
}
|
|
||||||
|
|
||||||
httpHandler, err := configureServerHandler(
|
|
||||||
srvCmdCfg,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to configure one of the RPC services <ERROR> %s", err)
|
t.Fatalf("Failed to configure one of the RPC services <ERROR> %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run TestServer.
|
// Run TestServer.
|
||||||
testServer.Server = httptest.NewUnstartedServer(httpHandler)
|
testServer.Server = httptest.NewUnstartedServer(httpHandler)
|
||||||
// obtain server address.
|
|
||||||
srvCmdCfg.serverAddr = testServer.Server.Listener.Addr().String()
|
|
||||||
|
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
globalObjectAPI = objLayer
|
globalObjectAPI = objLayer
|
||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
|
|
||||||
// initialize peer rpc
|
// initialize peer rpc
|
||||||
host, port, err := net.SplitHostPort(srvCmdCfg.serverAddr)
|
host, port := mustSplitHostPort(testServer.Server.Listener.Addr().String())
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Early setup error:", err)
|
|
||||||
}
|
|
||||||
globalMinioHost = host
|
globalMinioHost = host
|
||||||
globalMinioPort = port
|
globalMinioPort = port
|
||||||
globalMinioAddr = getLocalAddress(srvCmdCfg)
|
globalMinioAddr = getEndpointsLocalAddr(testServer.Disks)
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
initGlobalS3Peers(testServer.Disks)
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Early setup error:", err)
|
|
||||||
}
|
|
||||||
initGlobalS3Peers(endpoints)
|
|
||||||
|
|
||||||
return testServer
|
return testServer
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// testServerCertPEM and testServerKeyPEM are generated by
|
// testServerCertPEM and testServerKeyPEM are generated by
|
||||||
@ -339,10 +316,10 @@ func StartTestServer(t TestErrHandler, instanceType string) TestServer {
|
|||||||
|
|
||||||
// Initializes storage RPC endpoints.
|
// Initializes storage RPC endpoints.
|
||||||
// The object Layer will be a temp back used for testing purpose.
|
// 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.
|
// Initialize router.
|
||||||
muxRouter := router.NewRouter()
|
muxRouter := router.NewRouter()
|
||||||
registerStorageRPCRouters(muxRouter, srvCmdConfig)
|
registerStorageRPCRouters(muxRouter, endpoints)
|
||||||
return muxRouter
|
return muxRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,10 +331,6 @@ func StartTestStorageRPCServer(t TestErrHandler, instanceType string, diskN int)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Failed to create disks for the backend")
|
t.Fatal("Failed to create disks for the backend")
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
root, err := newTestConfig(globalMinioDefaultRegion)
|
root, err := newTestConfig(globalMinioDefaultRegion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -369,15 +342,14 @@ func StartTestStorageRPCServer(t TestErrHandler, instanceType string, diskN int)
|
|||||||
// Get credential.
|
// Get credential.
|
||||||
credentials := serverConfig.GetCredential()
|
credentials := serverConfig.GetCredential()
|
||||||
|
|
||||||
|
endpoints := mustGetNewEndpointList(disks...)
|
||||||
testRPCServer.Root = root
|
testRPCServer.Root = root
|
||||||
testRPCServer.Disks = endpoints
|
testRPCServer.Disks = endpoints
|
||||||
testRPCServer.AccessKey = credentials.AccessKey
|
testRPCServer.AccessKey = credentials.AccessKey
|
||||||
testRPCServer.SecretKey = credentials.SecretKey
|
testRPCServer.SecretKey = credentials.SecretKey
|
||||||
|
|
||||||
// Run TestServer.
|
// Run TestServer.
|
||||||
testRPCServer.Server = httptest.NewServer(initTestStorageRPCEndPoint(serverCmdConfig{
|
testRPCServer.Server = httptest.NewServer(initTestStorageRPCEndPoint(endpoints))
|
||||||
endpoints: endpoints,
|
|
||||||
}))
|
|
||||||
return testRPCServer
|
return testRPCServer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,10 +361,6 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Failed to create disks for the backend")
|
t.Fatal("Failed to create disks for the backend")
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
root, err := newTestConfig(globalMinioDefaultRegion)
|
root, err := newTestConfig(globalMinioDefaultRegion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -404,6 +372,7 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer {
|
|||||||
// Get credential.
|
// Get credential.
|
||||||
credentials := serverConfig.GetCredential()
|
credentials := serverConfig.GetCredential()
|
||||||
|
|
||||||
|
endpoints := mustGetNewEndpointList(disks...)
|
||||||
testRPCServer.Root = root
|
testRPCServer.Root = root
|
||||||
testRPCServer.Disks = endpoints
|
testRPCServer.Disks = endpoints
|
||||||
testRPCServer.AccessKey = credentials.AccessKey
|
testRPCServer.AccessKey = credentials.AccessKey
|
||||||
@ -420,13 +389,9 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer {
|
|||||||
testRPCServer.Obj = objLayer
|
testRPCServer.Obj = objLayer
|
||||||
globalObjLayerMutex.Unlock()
|
globalObjLayerMutex.Unlock()
|
||||||
|
|
||||||
srvCfg := serverCmdConfig{
|
|
||||||
endpoints: endpoints,
|
|
||||||
}
|
|
||||||
|
|
||||||
mux := router.NewRouter()
|
mux := router.NewRouter()
|
||||||
// need storage layer for bucket config storage.
|
// need storage layer for bucket config storage.
|
||||||
registerStorageRPCRouters(mux, srvCfg)
|
registerStorageRPCRouters(mux, endpoints)
|
||||||
// need API layer to send requests, etc.
|
// need API layer to send requests, etc.
|
||||||
registerAPIRouter(mux)
|
registerAPIRouter(mux)
|
||||||
// module being tested is Peer RPCs router.
|
// module being tested is Peer RPCs router.
|
||||||
@ -436,7 +401,7 @@ func StartTestPeersRPCServer(t TestErrHandler, instanceType string) TestServer {
|
|||||||
testRPCServer.Server = httptest.NewServer(mux)
|
testRPCServer.Server = httptest.NewServer(mux)
|
||||||
|
|
||||||
// initialize remainder of serverCmdConfig
|
// initialize remainder of serverCmdConfig
|
||||||
testRPCServer.SrvCmdCfg = srvCfg
|
testRPCServer.endpoints = endpoints
|
||||||
|
|
||||||
return testRPCServer
|
return testRPCServer
|
||||||
}
|
}
|
||||||
@ -481,7 +446,7 @@ func resetGlobalEventnotify() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resetGlobalEndpoints() {
|
func resetGlobalEndpoints() {
|
||||||
globalEndpoints = []*url.URL{}
|
globalEndpoints = EndpointList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetGlobalIsXL() {
|
func resetGlobalIsXL() {
|
||||||
@ -1659,7 +1624,7 @@ func getRandomDisks(N int) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initObjectLayer - Instantiates object layer and returns it.
|
// 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)
|
storageDisks, err := initStorageDisks(endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@ -1738,12 +1703,8 @@ func prepareXLStorageDisks(t *testing.T) ([]StorageAPI, []string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unexpected error: ", err)
|
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 {
|
if err != nil {
|
||||||
removeRoots(fsDirs)
|
removeRoots(fsDirs)
|
||||||
t.Fatal("Unable to initialize storage disks", err)
|
t.Fatal("Unable to initialize storage disks", err)
|
||||||
@ -2077,11 +2038,7 @@ func ExecObjectLayerStaleFilesTest(t *testing.T, objTest objTestStaleFilesType)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Initialization of disks for XL setup: %s", err)
|
t.Fatalf("Initialization of disks for XL setup: %s", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(erasureDisks)
|
objLayer, _, err := initObjectLayer(mustGetNewEndpointList(erasureDisks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Initialization of disks for XL setup: %s", err)
|
|
||||||
}
|
|
||||||
objLayer, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Initialization of object layer failed for XL setup: %s", err)
|
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
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tmp directory: %s", err)
|
t.Fatalf("Unable to create tmp directory: %s", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints([]string{fsDir})
|
endpoints := mustGetNewEndpointList(fsDir)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error %s", err)
|
|
||||||
}
|
|
||||||
disk, err := newStorageAPI(endpoints[0])
|
disk, err := newStorageAPI(endpoints[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create StorageAPI: %s", err)
|
t.Fatalf("Unable to create StorageAPI: %s", err)
|
||||||
@ -205,10 +202,7 @@ func TestTreeWalkTimeout(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tmp directory: %s", err)
|
t.Fatalf("Unable to create tmp directory: %s", err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints([]string{fsDir})
|
endpoints := mustGetNewEndpointList(fsDir)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error %s", err)
|
|
||||||
}
|
|
||||||
disk, err := newStorageAPI(endpoints[0])
|
disk, err := newStorageAPI(endpoints[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create StorageAPI: %s", err)
|
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)
|
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.
|
// Create two StorageAPIs disk1 and disk2.
|
||||||
|
endpoints := mustGetNewEndpointList(fsDir1)
|
||||||
disk1, err := newStorageAPI(endpoints[0])
|
disk1, err := newStorageAPI(endpoints[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unable to create StorageAPI: %s", err)
|
t.Errorf("Unable to create StorageAPI: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
disk2, err := newStorageAPI(endpoints[1])
|
endpoints = mustGetNewEndpointList(fsDir2)
|
||||||
|
disk2, err := newStorageAPI(endpoints[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unable to create StorageAPI: %s", err)
|
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)
|
t.Fatalf("Unable to create tmp directory: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints([]string{fsDir1})
|
endpoints := mustGetNewEndpointList(fsDir1)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error %s", err)
|
|
||||||
}
|
|
||||||
disk1, err := newStorageAPI(endpoints[0])
|
disk1, err := newStorageAPI(endpoints[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create StorageAPI: %s", err)
|
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)
|
t.Errorf("Unable to create tmp directory: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints([]string{fsDir1})
|
endpoints := mustGetNewEndpointList(fsDir1)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error %s", err)
|
|
||||||
}
|
|
||||||
disk1, err := newStorageAPI(endpoints[0])
|
disk1, err := newStorageAPI(endpoints[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create StorageAPI: %s", err)
|
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)
|
t.Errorf("Unable to create tmp directory: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints([]string{fsDir1})
|
endpoints := mustGetNewEndpointList(fsDir1)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error %s", err)
|
|
||||||
}
|
|
||||||
disk1, err := newStorageAPI(endpoints[0])
|
disk1, err := newStorageAPI(endpoints[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create StorageAPI: %s", err)
|
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
|
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.
|
// Convert url path into bucket and object name.
|
||||||
func urlPath2BucketObjectName(u *url.URL) (bucketName, objectName string) {
|
func urlPath2BucketObjectName(u *url.URL) (bucketName, objectName string) {
|
||||||
if u == nil {
|
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
|
// Split urlpath using slash separator into a given number of
|
||||||
// expected tokens.
|
// expected tokens.
|
||||||
tokens := splitStr(urlPath, slashSeparator, 2)
|
tokens := strings.SplitN(urlPath, slashSeparator, 2)
|
||||||
|
bucketName = tokens[0]
|
||||||
// Extract bucket and objects.
|
if len(tokens) == 2 {
|
||||||
bucketName, objectName = tokens[0], tokens[1]
|
objectName = tokens[1]
|
||||||
|
}
|
||||||
|
|
||||||
// Success.
|
// Success.
|
||||||
return bucketName, objectName
|
return bucketName, objectName
|
||||||
@ -110,29 +73,6 @@ const (
|
|||||||
httpsScheme = "https"
|
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.
|
// xmlDecoder provide decoded value in xml.
|
||||||
func xmlDecoder(body io.Reader, v interface{}, size int64) error {
|
func xmlDecoder(body io.Reader, v interface{}, size int64) error {
|
||||||
var lbody io.Reader
|
var lbody io.Reader
|
||||||
|
@ -18,12 +18,9 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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.
|
// Tests maximum object size.
|
||||||
func TestMaxObjectSize(t *testing.T) {
|
func TestMaxObjectSize(t *testing.T) {
|
||||||
sizes := []struct {
|
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.
|
// TestCheckURL tests valid url.
|
||||||
func TestCheckURL(t *testing.T) {
|
func TestCheckURL(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
@ -37,13 +37,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything is fine, should return nil
|
// Everything is fine, should return nil
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -59,13 +54,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disks 0..15 are nil
|
// Disks 0..15 are nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -84,13 +74,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk returns Faulty Disk
|
// One disk returns Faulty Disk
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -112,13 +97,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk is not found, heal corrupted disks should return nil
|
// One disk is not found, heal corrupted disks should return nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -134,13 +114,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove format.json of all disks
|
// Remove format.json of all disks
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -160,13 +135,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Corrupted format json in one disk
|
// Corrupted format json in one disk
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -186,13 +156,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove format.json on 3 disks.
|
// Remove format.json on 3 disks.
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -212,13 +177,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk is not found, heal corrupted disks should return nil
|
// One disk is not found, heal corrupted disks should return nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -246,13 +206,8 @@ func TestHealFormatXL(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk is not found, heal corrupted disks should return nil
|
// One disk is not found, heal corrupted disks should return nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -286,13 +241,8 @@ func TestUndoMakeBucket(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove format.json on 16 disks.
|
// Remove format.json on 16 disks.
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -331,13 +281,8 @@ func TestQuickHeal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove format.json on 16 disks.
|
// Remove format.json on 16 disks.
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -382,13 +327,8 @@ func TestQuickHeal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk is not found, heal corrupted disks should return nil
|
// One disk is not found, heal corrupted disks should return nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -404,13 +344,8 @@ func TestQuickHeal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err = parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// One disk is not found, heal corrupted disks should return nil
|
// One disk is not found, heal corrupted disks should return nil
|
||||||
obj, _, err = initObjectLayer(endpoints)
|
obj, _, err = initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -441,12 +376,7 @@ func TestListBucketsHeal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -505,13 +435,8 @@ func TestHealObjectXL(t *testing.T) {
|
|||||||
|
|
||||||
defer removeRoots(fsDirs)
|
defer removeRoots(fsDirs)
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything is fine, should return nil
|
// Everything is fine, should return nil
|
||||||
obj, _, err := initObjectLayer(endpoints)
|
obj, _, err := initObjectLayer(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -392,11 +392,7 @@ func TestShuffleDisks(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
endpoints, err := parseStorageEndpoints(disks)
|
objLayer, _, err := initObjectLayer(mustGetNewEndpointList(disks...))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
objLayer, _, err := initObjectLayer(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
removeRoots(disks)
|
removeRoots(disks)
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -50,12 +50,7 @@ func TestStorageInfo(t *testing.T) {
|
|||||||
t.Fatalf("Diskinfo total values should be greater 0")
|
t.Fatalf("Diskinfo total values should be greater 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(fsDirs)
|
storageDisks, err := initStorageDisks(mustGetNewEndpointList(fsDirs...))
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unexpected error %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
storageDisks, err := initStorageDisks(endpoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unexpected error: ", err)
|
t.Fatal("Unexpected error: ", err)
|
||||||
}
|
}
|
||||||
@ -145,11 +140,7 @@ func TestNewXL(t *testing.T) {
|
|||||||
t.Fatalf("Unable to initialize erasure, %s", err)
|
t.Fatalf("Unable to initialize erasure, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoints, err := parseStorageEndpoints(erasureDisks)
|
endpoints := mustGetNewEndpointList(erasureDisks...)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to initialize erasure, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
storageDisks, err := initStorageDisks(endpoints)
|
storageDisks, err := initStorageDisks(endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unexpected error: ", err)
|
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.
|
// StringSet - uses map as set of strings.
|
||||||
type StringSet map[string]struct{}
|
type StringSet map[string]struct{}
|
||||||
|
|
||||||
// keys - returns StringSet keys.
|
// ToSlice - returns StringSet as string slice.
|
||||||
func (set StringSet) keys() []string {
|
func (set StringSet) ToSlice() []string {
|
||||||
keys := make([]string, 0, len(set))
|
keys := make([]string, 0, len(set))
|
||||||
for k := range set {
|
for k := range set {
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
@ -141,7 +141,7 @@ func (set StringSet) Union(sset StringSet) StringSet {
|
|||||||
|
|
||||||
// MarshalJSON - converts to JSON data.
|
// MarshalJSON - converts to JSON data.
|
||||||
func (set StringSet) MarshalJSON() ([]byte, error) {
|
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.
|
// 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.
|
// String - returns printable string of the set.
|
||||||
func (set StringSet) String() string {
|
func (set StringSet) String() string {
|
||||||
return fmt.Sprintf("%s", set.keys())
|
return fmt.Sprintf("%s", set.ToSlice())
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStringSet - creates new string set.
|
// 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"
|
"revisionTime": "2016-12-20T20:43:13Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "A8QOw1aWwc+RtjGozY0XeS5varo=",
|
"checksumSHA1": "maUy+dbN6VfTTnfErrAW2lLit1w=",
|
||||||
"path": "github.com/minio/minio-go/pkg/set",
|
"path": "github.com/minio/minio-go/pkg/set",
|
||||||
"revision": "9e734013294ab153b0bdbe182738bcddd46f1947",
|
"revision": "7a3619e41885dcbcfafee193c10eb80530c2be53",
|
||||||
"revisionTime": "2016-08-18T00:31:20Z"
|
"revisionTime": "2017-02-17T20:03:45Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "URVle4qtadmW9w9BulDRHY3kxnA=",
|
"checksumSHA1": "URVle4qtadmW9w9BulDRHY3kxnA=",
|
||||||
|
Loading…
Reference in New Issue
Block a user