mirror of
				https://github.com/minio/minio.git
				synced 2025-10-29 15:55:00 -04:00 
			
		
		
		
	Move remote disk StorageAPI abstraction from RPC to REST (#6464)
This commit is contained in:
		
							parent
							
								
									670f9788e3
								
							
						
					
					
						commit
						81bee93b8d
					
				| @ -100,7 +100,7 @@ func newStorageAPI(endpoint Endpoint) (storage StorageAPI, err error) { | ||||
| 		return newPosix(endpoint.Path) | ||||
| 	} | ||||
| 
 | ||||
| 	return newStorageRPC(endpoint), nil | ||||
| 	return newStorageRESTClient(endpoint), nil | ||||
| } | ||||
| 
 | ||||
| // Cleanup a directory recursively. | ||||
|  | ||||
							
								
								
									
										108
									
								
								cmd/rest/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								cmd/rest/client.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 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 rest | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"time" | ||||
| 
 | ||||
| 	xhttp "github.com/minio/minio/cmd/http" | ||||
| ) | ||||
| 
 | ||||
| // DefaultRESTTimeout - default RPC timeout is one minute. | ||||
| const DefaultRESTTimeout = 1 * time.Minute | ||||
| 
 | ||||
| // Client - http based RPC client. | ||||
| type Client struct { | ||||
| 	httpClient   *http.Client | ||||
| 	url          *url.URL | ||||
| 	newAuthToken func() string | ||||
| } | ||||
| 
 | ||||
| // Call - make a REST call. | ||||
| func (c *Client) Call(method string, values url.Values, body io.Reader) (reply io.ReadCloser, err error) { | ||||
| 	req, err := http.NewRequest(http.MethodPost, c.url.String()+"/"+method+"?"+values.Encode(), body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	req.Header.Set("Authorization", "Bearer "+c.newAuthToken()) | ||||
| 	req.Header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339)) | ||||
| 
 | ||||
| 	resp, err := c.httpClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		// Limit the ReadAll(), just in case, because of a bug, the server responds with large data. | ||||
| 		r := io.LimitReader(resp.Body, 1024) | ||||
| 		b, err := ioutil.ReadAll(r) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return nil, errors.New(string(b)) | ||||
| 	} | ||||
| 	return resp.Body, nil | ||||
| } | ||||
| 
 | ||||
| func newCustomDialContext(timeout time.Duration) func(ctx context.Context, network, addr string) (net.Conn, error) { | ||||
| 	return func(ctx context.Context, network, addr string) (net.Conn, error) { | ||||
| 		dialer := &net.Dialer{ | ||||
| 			Timeout:   timeout, | ||||
| 			KeepAlive: timeout, | ||||
| 			DualStack: true, | ||||
| 		} | ||||
| 
 | ||||
| 		conn, err := dialer.DialContext(ctx, network, addr) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		return xhttp.NewTimeoutConn(conn, timeout, timeout), nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NewClient - returns new RPC client. | ||||
| func NewClient(url *url.URL, tlsConfig *tls.Config, timeout time.Duration, newAuthToken func() string) *Client { | ||||
| 	return &Client{ | ||||
| 		httpClient: &http.Client{ | ||||
| 			// Transport is exactly same as Go default in https://golang.org/pkg/net/http/#RoundTripper | ||||
| 			// except custom DialContext and TLSClientConfig. | ||||
| 			Transport: &http.Transport{ | ||||
| 				Proxy:                 http.ProxyFromEnvironment, | ||||
| 				DialContext:           newCustomDialContext(timeout), | ||||
| 				MaxIdleConnsPerHost:   4096, | ||||
| 				MaxIdleConns:          4096, | ||||
| 				IdleConnTimeout:       90 * time.Second, | ||||
| 				TLSHandshakeTimeout:   10 * time.Second, | ||||
| 				ExpectContinueTimeout: 1 * time.Second, | ||||
| 				TLSClientConfig:       tlsConfig, | ||||
| 				DisableCompression:    true, | ||||
| 			}, | ||||
| 		}, | ||||
| 		url:          url, | ||||
| 		newAuthToken: newAuthToken, | ||||
| 	} | ||||
| } | ||||
| @ -36,7 +36,7 @@ func newCacheObjectsFn() CacheObjectLayer { | ||||
| // Composed function registering routers for only distributed XL setup. | ||||
| func registerDistXLRouters(router *mux.Router, endpoints EndpointList) { | ||||
| 	// Register storage rpc router only if its a distributed setup. | ||||
| 	registerStorageRPCRouters(router, endpoints) | ||||
| 	registerStorageRESTHandlers(router, endpoints) | ||||
| 
 | ||||
| 	// Register distributed namespace lock. | ||||
| 	registerDistNSLockRouter(router) | ||||
|  | ||||
							
								
								
									
										343
									
								
								cmd/storage-rest-client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								cmd/storage-rest-client.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,343 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 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 ( | ||||
| 	"bytes" | ||||
| 	"crypto/tls" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"encoding/gob" | ||||
| 	"encoding/hex" | ||||
| 
 | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	"github.com/minio/minio/cmd/rest" | ||||
| 	xnet "github.com/minio/minio/pkg/net" | ||||
| ) | ||||
| 
 | ||||
| func isNetworkDisconnectError(err error) bool { | ||||
| 	if err == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	if uerr, isURLError := err.(*url.Error); isURLError { | ||||
| 		if uerr.Timeout() { | ||||
| 			return true | ||||
| 		} | ||||
| 
 | ||||
| 		err = uerr.Err | ||||
| 	} | ||||
| 
 | ||||
| 	_, isNetOpError := err.(*net.OpError) | ||||
| 	return isNetOpError | ||||
| } | ||||
| 
 | ||||
| // Converts rpc.ServerError to underlying error. This function is | ||||
| // written so that the storageAPI errors are consistent across network | ||||
| // disks as well. | ||||
| func toStorageErr(err error) error { | ||||
| 	if err == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	if isNetworkDisconnectError(err) { | ||||
| 		return errDiskNotFound | ||||
| 	} | ||||
| 
 | ||||
| 	switch err.Error() { | ||||
| 	case io.EOF.Error(): | ||||
| 		return io.EOF | ||||
| 	case io.ErrUnexpectedEOF.Error(): | ||||
| 		return io.ErrUnexpectedEOF | ||||
| 	case errUnexpected.Error(): | ||||
| 		return errUnexpected | ||||
| 	case errDiskFull.Error(): | ||||
| 		return errDiskFull | ||||
| 	case errVolumeNotFound.Error(): | ||||
| 		return errVolumeNotFound | ||||
| 	case errVolumeExists.Error(): | ||||
| 		return errVolumeExists | ||||
| 	case errFileNotFound.Error(): | ||||
| 		return errFileNotFound | ||||
| 	case errFileNameTooLong.Error(): | ||||
| 		return errFileNameTooLong | ||||
| 	case errFileAccessDenied.Error(): | ||||
| 		return errFileAccessDenied | ||||
| 	case errIsNotRegular.Error(): | ||||
| 		return errIsNotRegular | ||||
| 	case errVolumeNotEmpty.Error(): | ||||
| 		return errVolumeNotEmpty | ||||
| 	case errVolumeAccessDenied.Error(): | ||||
| 		return errVolumeAccessDenied | ||||
| 	case errCorruptedFormat.Error(): | ||||
| 		return errCorruptedFormat | ||||
| 	case errUnformattedDisk.Error(): | ||||
| 		return errUnformattedDisk | ||||
| 	case errInvalidAccessKeyID.Error(): | ||||
| 		return errInvalidAccessKeyID | ||||
| 	case errAuthentication.Error(): | ||||
| 		return errAuthentication | ||||
| 	case errRPCAPIVersionUnsupported.Error(): | ||||
| 		return errRPCAPIVersionUnsupported | ||||
| 	case errServerTimeMismatch.Error(): | ||||
| 		return errServerTimeMismatch | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Abstracts a remote disk. | ||||
| type storageRESTClient struct { | ||||
| 	endpoint   Endpoint | ||||
| 	restClient *rest.Client | ||||
| 	connected  bool | ||||
| 	lastError  error | ||||
| } | ||||
| 
 | ||||
| // Wrapper to restClient.Call to handle network errors, in case of network error the connection is makred disconnected | ||||
| // permanently. The only way to restore the storage connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints() | ||||
| // after verifying format.json | ||||
| func (client *storageRESTClient) call(method string, values url.Values, body io.Reader) (respBody io.ReadCloser, err error) { | ||||
| 	if !client.connected { | ||||
| 		return nil, errDiskNotFound | ||||
| 	} | ||||
| 	respBody, err = client.restClient.Call(method, values, body) | ||||
| 	if err == nil { | ||||
| 		return respBody, nil | ||||
| 	} | ||||
| 	client.lastError = err | ||||
| 	if isNetworkDisconnectError(err) { | ||||
| 		client.connected = false | ||||
| 	} | ||||
| 
 | ||||
| 	return nil, toStorageErr(err) | ||||
| } | ||||
| 
 | ||||
| // Stringer provides a canonicalized representation of network device. | ||||
| func (client *storageRESTClient) String() string { | ||||
| 	return client.endpoint.String() | ||||
| } | ||||
| 
 | ||||
| // IsOnline - returns whether RPC client failed to connect or not. | ||||
| func (client *storageRESTClient) IsOnline() bool { | ||||
| 	return client.connected | ||||
| } | ||||
| 
 | ||||
| // LastError - returns the network error if any. | ||||
| func (client *storageRESTClient) LastError() error { | ||||
| 	return client.lastError | ||||
| } | ||||
| 
 | ||||
| // DiskInfo - fetch disk information for a remote disk. | ||||
| func (client *storageRESTClient) DiskInfo() (info DiskInfo, err error) { | ||||
| 	respBody, err := client.call(storageRESTMethodDiskInfo, nil, nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer CloseResponse(respBody) | ||||
| 	err = gob.NewDecoder(respBody).Decode(&info) | ||||
| 	return info, err | ||||
| } | ||||
| 
 | ||||
| // MakeVol - create a volume on a remote disk. | ||||
| func (client *storageRESTClient) MakeVol(volume string) (err error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	respBody, err := client.call(storageRESTMethodMakeVol, values, nil) | ||||
| 	defer CloseResponse(respBody) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // ListVols - List all volumes on a remote disk. | ||||
| func (client *storageRESTClient) ListVols() (volinfo []VolInfo, err error) { | ||||
| 	respBody, err := client.call(storageRESTMethodListVols, nil, nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer CloseResponse(respBody) | ||||
| 	err = gob.NewDecoder(respBody).Decode(&volinfo) | ||||
| 	return volinfo, err | ||||
| } | ||||
| 
 | ||||
| // StatVol - get volume info over the network. | ||||
| func (client *storageRESTClient) StatVol(volume string) (volInfo VolInfo, err error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	respBody, err := client.call(storageRESTMethodStatVol, values, nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer CloseResponse(respBody) | ||||
| 	err = gob.NewDecoder(respBody).Decode(&volInfo) | ||||
| 	return volInfo, err | ||||
| } | ||||
| 
 | ||||
| // DeleteVol - Deletes a volume over the network. | ||||
| func (client *storageRESTClient) DeleteVol(volume string) (err error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	respBody, err := client.call(storageRESTMethodDeleteVol, values, nil) | ||||
| 	defer CloseResponse(respBody) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // PrepareFile - to fallocate() disk space for a file. | ||||
| func (client *storageRESTClient) PrepareFile(volume, path string, length int64) error { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	values.Set(storageRESTFilePath, path) | ||||
| 	values.Set(storageRESTLength, strconv.Itoa(int(length))) | ||||
| 	respBody, err := client.call(storageRESTMethodPrepareFile, values, nil) | ||||
| 	defer CloseResponse(respBody) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // AppendFile - append to a file. | ||||
| func (client *storageRESTClient) AppendFile(volume, path string, buffer []byte) error { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	values.Set(storageRESTFilePath, path) | ||||
| 	reader := bytes.NewBuffer(buffer) | ||||
| 	respBody, err := client.call(storageRESTMethodAppendFile, values, reader) | ||||
| 	defer CloseResponse(respBody) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // StatFile - stat a file. | ||||
| func (client *storageRESTClient) StatFile(volume, path string) (info FileInfo, err error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	values.Set(storageRESTFilePath, path) | ||||
| 	respBody, err := client.call(storageRESTMethodStatFile, values, nil) | ||||
| 	if err != nil { | ||||
| 		return info, err | ||||
| 	} | ||||
| 	defer CloseResponse(respBody) | ||||
| 	err = gob.NewDecoder(respBody).Decode(&info) | ||||
| 	return info, err | ||||
| } | ||||
| 
 | ||||
| // ReadAll - reads all contents of a file. | ||||
| func (client *storageRESTClient) ReadAll(volume, path string) ([]byte, error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	values.Set(storageRESTFilePath, path) | ||||
| 	respBody, err := client.call(storageRESTMethodReadAll, values, nil) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer CloseResponse(respBody) | ||||
| 	return ioutil.ReadAll(respBody) | ||||
| } | ||||
| 
 | ||||
| // ReadFile - reads section of a file. | ||||
| func (client *storageRESTClient) ReadFile(volume, path string, offset int64, buffer []byte, verifier *BitrotVerifier) (int64, error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	values.Set(storageRESTFilePath, path) | ||||
| 	values.Set(storageRESTOffset, strconv.Itoa(int(offset))) | ||||
| 	values.Set(storageRESTLength, strconv.Itoa(len(buffer))) | ||||
| 	if verifier != nil { | ||||
| 		values.Set(storageRESTBitrotAlgo, verifier.algorithm.String()) | ||||
| 		values.Set(storageRESTBitrotHash, hex.EncodeToString(verifier.sum)) | ||||
| 	} else { | ||||
| 		values.Set(storageRESTBitrotAlgo, "") | ||||
| 		values.Set(storageRESTBitrotHash, "") | ||||
| 	} | ||||
| 	respBody, err := client.call(storageRESTMethodReadFile, values, nil) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	defer CloseResponse(respBody) | ||||
| 	n, err := io.ReadFull(respBody, buffer) | ||||
| 	return int64(n), err | ||||
| } | ||||
| 
 | ||||
| // ListDir - lists a directory. | ||||
| func (client *storageRESTClient) ListDir(volume, dirPath string, count int) (entries []string, err error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	values.Set(storageRESTDirPath, dirPath) | ||||
| 	values.Set(storageRESTCount, strconv.Itoa(count)) | ||||
| 	respBody, err := client.call(storageRESTMethodListDir, values, nil) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer CloseResponse(respBody) | ||||
| 	err = gob.NewDecoder(respBody).Decode(&entries) | ||||
| 	return entries, err | ||||
| } | ||||
| 
 | ||||
| // DeleteFile - deletes a file. | ||||
| func (client *storageRESTClient) DeleteFile(volume, path string) error { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTVolume, volume) | ||||
| 	values.Set(storageRESTFilePath, path) | ||||
| 	respBody, err := client.call(storageRESTMethodDeleteFile, values, nil) | ||||
| 	defer CloseResponse(respBody) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // RenameFile - renames a file. | ||||
| func (client *storageRESTClient) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) (err error) { | ||||
| 	values := make(url.Values) | ||||
| 	values.Set(storageRESTSrcVolume, srcVolume) | ||||
| 	values.Set(storageRESTSrcPath, srcPath) | ||||
| 	values.Set(storageRESTDstVolume, dstVolume) | ||||
| 	values.Set(storageRESTDstPath, dstPath) | ||||
| 	respBody, err := client.call(storageRESTMethodRenameFile, values, nil) | ||||
| 	defer CloseResponse(respBody) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Close - marks the client as closed. | ||||
| func (client *storageRESTClient) Close() error { | ||||
| 	client.connected = false | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Returns a storage rest client. | ||||
| func newStorageRESTClient(endpoint Endpoint) *storageRESTClient { | ||||
| 	host, err := xnet.ParseHost(endpoint.Host) | ||||
| 	logger.FatalIf(err, "Unable to parse storage Host") | ||||
| 
 | ||||
| 	scheme := "http" | ||||
| 	if globalIsSSL { | ||||
| 		scheme = "https" | ||||
| 	} | ||||
| 
 | ||||
| 	serverURL := &url.URL{ | ||||
| 		Scheme: scheme, | ||||
| 		Host:   endpoint.Host, | ||||
| 		Path:   path.Join(storageRESTPath, endpoint.Path), | ||||
| 	} | ||||
| 
 | ||||
| 	var tlsConfig *tls.Config | ||||
| 	if globalIsSSL { | ||||
| 		tlsConfig = &tls.Config{ | ||||
| 			ServerName: host.Name, | ||||
| 			RootCAs:    globalRootCAs, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	restClient := rest.NewClient(serverURL, tlsConfig, rest.DefaultRESTTimeout, newAuthToken) | ||||
| 	return &storageRESTClient{endpoint: endpoint, restClient: restClient, connected: true} | ||||
| } | ||||
							
								
								
									
										52
									
								
								cmd/storage-rest-common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								cmd/storage-rest-common.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 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 | ||||
| 
 | ||||
| const storageRESTVersion = "v1" | ||||
| const storageRESTPath = minioReservedBucketPath + "/storage/" + storageRESTVersion + "/" | ||||
| 
 | ||||
| const ( | ||||
| 	storageRESTMethodDiskInfo  = "diskinfo" | ||||
| 	storageRESTMethodMakeVol   = "makevol" | ||||
| 	storageRESTMethodStatVol   = "statvol" | ||||
| 	storageRESTMethodDeleteVol = "deletevol" | ||||
| 	storageRESTMethodListVols  = "listvols" | ||||
| 
 | ||||
| 	storageRESTMethodPrepareFile = "preparefile" | ||||
| 	storageRESTMethodAppendFile  = "appendfile" | ||||
| 	storageRESTMethodStatFile    = "statfile" | ||||
| 	storageRESTMethodReadAll     = "readall" | ||||
| 	storageRESTMethodReadFile    = "readfile" | ||||
| 	storageRESTMethodListDir     = "listdir" | ||||
| 	storageRESTMethodDeleteFile  = "deletefile" | ||||
| 	storageRESTMethodRenameFile  = "renamefile" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	storageRESTVolume     = "volume" | ||||
| 	storageRESTDirPath    = "dir-path" | ||||
| 	storageRESTFilePath   = "file-path" | ||||
| 	storageRESTSrcVolume  = "source-volume" | ||||
| 	storageRESTSrcPath    = "source-path" | ||||
| 	storageRESTDstVolume  = "destination-volume" | ||||
| 	storageRESTDstPath    = "destination-path" | ||||
| 	storageRESTOffset     = "offset" | ||||
| 	storageRESTLength     = "length" | ||||
| 	storageRESTCount      = "count" | ||||
| 	storageRESTBitrotAlgo = "bitrot-algo" | ||||
| 	storageRESTBitrotHash = "bitrot-hash" | ||||
| ) | ||||
							
								
								
									
										353
									
								
								cmd/storage-rest-server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										353
									
								
								cmd/storage-rest-server.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,353 @@ | ||||
| /* | ||||
|  * Minio Cloud Storage, (C) 2018 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" | ||||
| 	"io" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"encoding/gob" | ||||
| 	"encoding/hex" | ||||
| 
 | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| ) | ||||
| 
 | ||||
| // To abstract a disk over network. | ||||
| type storageRESTServer struct { | ||||
| 	storage *posix | ||||
| } | ||||
| 
 | ||||
| func (s *storageRESTServer) writeErrorResponse(w http.ResponseWriter, err error) { | ||||
| 	w.WriteHeader(http.StatusForbidden) | ||||
| 	w.Write([]byte(err.Error())) | ||||
| } | ||||
| 
 | ||||
| // IsValid - To authenticate and verify the time difference. | ||||
| func (s *storageRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool { | ||||
| 	requestTimeStr := r.Header.Get("X-Minio-Time") | ||||
| 	requestTime, err := time.Parse(time.RFC3339, requestTimeStr) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return false | ||||
| 	} | ||||
| 	utcNow := UTCNow() | ||||
| 	delta := requestTime.Sub(utcNow) | ||||
| 	if delta < 0 { | ||||
| 		delta = delta * -1 | ||||
| 	} | ||||
| 	if delta > DefaultSkewTime { | ||||
| 		s.writeErrorResponse(w, fmt.Errorf("client time %v is too apart with server time %v", requestTime, utcNow)) | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // DiskInfoHandler - returns disk info. | ||||
| func (s *storageRESTServer) DiskInfoHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	info, err := s.storage.DiskInfo() | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer w.(http.Flusher).Flush() | ||||
| 	gob.NewEncoder(w).Encode(info) | ||||
| } | ||||
| 
 | ||||
| // MakeVolHandler - make a volume. | ||||
| func (s *storageRESTServer) MakeVolHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	err := s.storage.MakeVol(volume) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // ListVolsHandler - list volumes. | ||||
| func (s *storageRESTServer) ListVolsHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	infos, err := s.storage.ListVols() | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer w.(http.Flusher).Flush() | ||||
| 	gob.NewEncoder(w).Encode(&infos) | ||||
| } | ||||
| 
 | ||||
| // StatVolHandler - stat a volume. | ||||
| func (s *storageRESTServer) StatVolHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	info, err := s.storage.StatVol(volume) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer w.(http.Flusher).Flush() | ||||
| 	gob.NewEncoder(w).Encode(info) | ||||
| } | ||||
| 
 | ||||
| // DeleteVolumeHandler - delete a volume. | ||||
| func (s *storageRESTServer) DeleteVolHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	err := s.storage.DeleteVol(volume) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // PrepareFileHandler - fallocate() space for a file. | ||||
| func (s *storageRESTServer) PrepareFileHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	filePath := vars[storageRESTFilePath] | ||||
| 	fileSizeStr := vars[storageRESTLength] | ||||
| 	fileSize, err := strconv.Atoi(fileSizeStr) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	err = s.storage.PrepareFile(volume, filePath, int64(fileSize)) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AppendFileHandler - append to a file. | ||||
| func (s *storageRESTServer) AppendFileHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	filePath := vars[storageRESTFilePath] | ||||
| 
 | ||||
| 	if r.ContentLength < 0 { | ||||
| 		s.writeErrorResponse(w, errInvalidArgument) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	buf := make([]byte, r.ContentLength) | ||||
| 	_, err := io.ReadFull(r.Body, buf) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	err = s.storage.AppendFile(volume, filePath, buf) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // StatFileHandler - stat a file. | ||||
| func (s *storageRESTServer) StatFileHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	filePath := vars[storageRESTFilePath] | ||||
| 
 | ||||
| 	info, err := s.storage.StatFile(volume, filePath) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer w.(http.Flusher).Flush() | ||||
| 	gob.NewEncoder(w).Encode(info) | ||||
| } | ||||
| 
 | ||||
| // ReadAllHandler - read all the contents of a file. | ||||
| func (s *storageRESTServer) ReadAllHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	filePath := vars[storageRESTFilePath] | ||||
| 
 | ||||
| 	buf, err := s.storage.ReadAll(volume, filePath) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	w.Header().Set("Content-Length", strconv.Itoa(len(buf))) | ||||
| 	w.Write(buf) | ||||
| } | ||||
| 
 | ||||
| // ReadFileHandler - read section of a file. | ||||
| func (s *storageRESTServer) ReadFileHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	filePath := vars[storageRESTFilePath] | ||||
| 	offset, err := strconv.Atoi(vars[storageRESTOffset]) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	length, err := strconv.Atoi(vars[storageRESTLength]) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	if offset < 0 || length < 0 { | ||||
| 		s.writeErrorResponse(w, errInvalidArgument) | ||||
| 		return | ||||
| 	} | ||||
| 	var verifier *BitrotVerifier | ||||
| 	if vars[storageRESTBitrotAlgo] != "" { | ||||
| 		hashStr := vars[storageRESTBitrotHash] | ||||
| 		var hash []byte | ||||
| 		hash, err = hex.DecodeString(hashStr) | ||||
| 		if err != nil { | ||||
| 			s.writeErrorResponse(w, err) | ||||
| 			return | ||||
| 		} | ||||
| 		verifier = NewBitrotVerifier(BitrotAlgorithmFromString(vars[storageRESTBitrotAlgo]), hash) | ||||
| 	} | ||||
| 	buf := make([]byte, length) | ||||
| 	_, err = s.storage.ReadFile(volume, filePath, int64(offset), buf, verifier) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	w.Header().Set("Content-Length", strconv.Itoa(len(buf))) | ||||
| 	w.Write(buf) | ||||
| } | ||||
| 
 | ||||
| // ListDirHandler - list a directory. | ||||
| func (s *storageRESTServer) ListDirHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	dirPath := vars[storageRESTDirPath] | ||||
| 	count, err := strconv.Atoi(vars[storageRESTCount]) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	entries, err := s.storage.ListDir(volume, dirPath, count) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer w.(http.Flusher).Flush() | ||||
| 	gob.NewEncoder(w).Encode(&entries) | ||||
| } | ||||
| 
 | ||||
| // DeleteFileHandler - delete a file. | ||||
| func (s *storageRESTServer) DeleteFileHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	volume := vars[storageRESTVolume] | ||||
| 	filePath := vars[storageRESTFilePath] | ||||
| 
 | ||||
| 	err := s.storage.DeleteFile(volume, filePath) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // RenameFileHandler - rename a file. | ||||
| func (s *storageRESTServer) RenameFileHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !s.IsValid(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	srcVolume := vars[storageRESTSrcVolume] | ||||
| 	srcFilePath := vars[storageRESTSrcPath] | ||||
| 	dstVolume := vars[storageRESTDstVolume] | ||||
| 	dstFilePath := vars[storageRESTDstPath] | ||||
| 	err := s.storage.RenameFile(srcVolume, srcFilePath, dstVolume, dstFilePath) | ||||
| 	if err != nil { | ||||
| 		s.writeErrorResponse(w, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // registerStorageRPCRouter - register storage rpc router. | ||||
| func registerStorageRESTHandlers(router *mux.Router, endpoints EndpointList) { | ||||
| 	for _, endpoint := range endpoints { | ||||
| 		if !endpoint.IsLocal { | ||||
| 			continue | ||||
| 		} | ||||
| 		storage, err := newPosix(endpoint.Path) | ||||
| 		if err != nil { | ||||
| 			logger.Fatal(uiErrUnableToWriteInBackend(err), "Unable to initialize posix backend") | ||||
| 		} | ||||
| 
 | ||||
| 		server := &storageRESTServer{storage} | ||||
| 
 | ||||
| 		subrouter := router.PathPrefix(path.Join(storageRESTPath, endpoint.Path)).Subrouter() | ||||
| 
 | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodDiskInfo).HandlerFunc(httpTraceHdrs(server.DiskInfoHandler)) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodMakeVol).HandlerFunc(httpTraceHdrs(server.MakeVolHandler)).Queries(restQueries(storageRESTVolume)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodStatVol).HandlerFunc(httpTraceHdrs(server.StatVolHandler)).Queries(restQueries(storageRESTVolume)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodDeleteVol).HandlerFunc(httpTraceHdrs(server.DeleteVolHandler)).Queries(restQueries(storageRESTVolume)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodListVols).HandlerFunc(httpTraceHdrs(server.ListVolsHandler)) | ||||
| 
 | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodPrepareFile).HandlerFunc(httpTraceHdrs(server.PrepareFileHandler)). | ||||
| 			Queries(restQueries(storageRESTVolume, storageRESTFilePath, storageRESTLength)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodAppendFile).HandlerFunc(httpTraceHdrs(server.AppendFileHandler)). | ||||
| 			Queries(restQueries(storageRESTVolume, storageRESTFilePath)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodStatFile).HandlerFunc(httpTraceHdrs(server.StatFileHandler)). | ||||
| 			Queries(restQueries(storageRESTVolume, storageRESTFilePath)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodReadAll).HandlerFunc(httpTraceHdrs(server.ReadAllHandler)). | ||||
| 			Queries(restQueries(storageRESTVolume, storageRESTFilePath)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodReadFile).HandlerFunc(httpTraceHdrs(server.ReadFileHandler)). | ||||
| 			Queries(restQueries(storageRESTVolume, storageRESTFilePath, storageRESTOffset, storageRESTLength, storageRESTBitrotAlgo, storageRESTBitrotHash)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodListDir).HandlerFunc(httpTraceHdrs(server.ListDirHandler)). | ||||
| 			Queries(restQueries(storageRESTVolume, storageRESTDirPath, storageRESTCount)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodDeleteFile).HandlerFunc(httpTraceHdrs(server.DeleteFileHandler)). | ||||
| 			Queries(restQueries(storageRESTVolume, storageRESTFilePath)...) | ||||
| 		subrouter.Methods(http.MethodPost).Path("/" + storageRESTMethodRenameFile).HandlerFunc(httpTraceHdrs(server.RenameFileHandler)). | ||||
| 			Queries(restQueries(storageRESTSrcVolume, storageRESTSrcPath, storageRESTDstVolume, storageRESTDstPath)...) | ||||
| 
 | ||||
| 	} | ||||
| } | ||||
| @ -18,18 +18,18 @@ package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/gorilla/mux" | ||||
| 	xnet "github.com/minio/minio/pkg/net" | ||||
| ) | ||||
| 
 | ||||
| /////////////////////////////////////////////////////////////////////////////// | ||||
| // | ||||
| // Storage RPC server, storageRPCReceiver and StorageRPCClient are | ||||
| // Storage REST server, storageRESTReceiver and StorageRESTClient are | ||||
| // inter-dependent, below test functions are sufficient to test all of them. | ||||
| // | ||||
| /////////////////////////////////////////////////////////////////////////////// | ||||
| @ -520,182 +520,174 @@ func testStorageAPIRenameFile(t *testing.T, storage StorageAPI) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func newStorageRPCHTTPServerClient(t *testing.T) (*httptest.Server, *StorageRPCClient, *serverConfig, string) { | ||||
| 	endpointPath, err := ioutil.TempDir("", ".TestStorageRPC.") | ||||
| func newStorageRESTHTTPServerClient(t *testing.T) (*httptest.Server, *storageRESTClient, *serverConfig, string) { | ||||
| 	endpointPath, err := ioutil.TempDir("", ".TestStorageREST.") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	rpcServer, err := NewStorageRPCServer(endpointPath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		rpcServer.ServeHTTP(w, r) | ||||
| 	})) | ||||
| 	router := mux.NewRouter() | ||||
| 	httpServer := httptest.NewServer(router) | ||||
| 
 | ||||
| 	url, err := xnet.ParseURL(httpServer.URL) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error %v", err) | ||||
| 	} | ||||
| 	url.Path = endpointPath | ||||
| 
 | ||||
| 	host, err := xnet.ParseHost(url.Host) | ||||
| 	endpoint, err := NewEndpoint(url.String()) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error %v", err) | ||||
| 		t.Fatalf("NewEndpoint failed %v", endpoint) | ||||
| 	} | ||||
| 
 | ||||
| 	registerStorageRESTHandlers(router, EndpointList{endpoint}) | ||||
| 	restClient := newStorageRESTClient(endpoint) | ||||
| 
 | ||||
| 	prevGlobalServerConfig := globalServerConfig | ||||
| 	globalServerConfig = newServerConfig() | ||||
| 
 | ||||
| 	rpcClient, err := NewStorageRPCClient(host, endpointPath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error %v", err) | ||||
| 	} | ||||
| 	rpcClient.connected = true | ||||
| 
 | ||||
| 	return httpServer, rpcClient, prevGlobalServerConfig, endpointPath | ||||
| 	return httpServer, restClient, prevGlobalServerConfig, endpointPath | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientDiskInfo(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientDiskInfo(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIDiskInfo(t, rpcClient) | ||||
| 	testStorageAPIDiskInfo(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientMakeVol(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientMakeVol(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIMakeVol(t, rpcClient) | ||||
| 	testStorageAPIMakeVol(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientListVols(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientListVols(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIListVols(t, rpcClient) | ||||
| 	testStorageAPIListVols(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientStatVol(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientStatVol(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIStatVol(t, rpcClient) | ||||
| 	testStorageAPIStatVol(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientDeleteVol(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientDeleteVol(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIDeleteVol(t, rpcClient) | ||||
| 	testStorageAPIDeleteVol(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientStatFile(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientStatFile(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIStatFile(t, rpcClient) | ||||
| 	testStorageAPIStatFile(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientListDir(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientListDir(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIListDir(t, rpcClient) | ||||
| 	testStorageAPIListDir(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientReadAll(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientReadAll(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIReadAll(t, rpcClient) | ||||
| 	testStorageAPIReadAll(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientReadFile(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientReadFile(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIReadFile(t, rpcClient) | ||||
| 	testStorageAPIReadFile(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientPrepareFile(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientPrepareFile(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIPrepareFile(t, rpcClient) | ||||
| 	testStorageAPIPrepareFile(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientAppendFile(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientAppendFile(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIAppendFile(t, rpcClient) | ||||
| 	testStorageAPIAppendFile(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientDeleteFile(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientDeleteFile(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIDeleteFile(t, rpcClient) | ||||
| 	testStorageAPIDeleteFile(t, restClient) | ||||
| } | ||||
| 
 | ||||
| func TestStorageRPCClientRenameFile(t *testing.T) { | ||||
| 	httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t) | ||||
| func TestStorageRESTClientRenameFile(t *testing.T) { | ||||
| 	httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t) | ||||
| 	defer httpServer.Close() | ||||
| 	defer func() { | ||||
| 		globalServerConfig = prevGlobalServerConfig | ||||
| 	}() | ||||
| 	defer os.RemoveAll(endpointPath) | ||||
| 
 | ||||
| 	testStorageAPIRenameFile(t, rpcClient) | ||||
| 	testStorageAPIRenameFile(t, restClient) | ||||
| } | ||||
| @ -1,338 +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 ( | ||||
| 	"bytes" | ||||
| 	"crypto/tls" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	xnet "github.com/minio/minio/pkg/net" | ||||
| ) | ||||
| 
 | ||||
| func isNetworkDisconnectError(err error) bool { | ||||
| 	if err == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	if uerr, isURLError := err.(*url.Error); isURLError { | ||||
| 		if uerr.Timeout() { | ||||
| 			return true | ||||
| 		} | ||||
| 
 | ||||
| 		err = uerr.Err | ||||
| 	} | ||||
| 
 | ||||
| 	_, isNetOpError := err.(*net.OpError) | ||||
| 	return isNetOpError | ||||
| } | ||||
| 
 | ||||
| // Converts rpc.ServerError to underlying error. This function is | ||||
| // written so that the storageAPI errors are consistent across network | ||||
| // disks as well. | ||||
| func toStorageErr(err error) error { | ||||
| 	if err == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	if isNetworkDisconnectError(err) { | ||||
| 		return errDiskNotFound | ||||
| 	} | ||||
| 
 | ||||
| 	switch err.Error() { | ||||
| 	case io.EOF.Error(): | ||||
| 		return io.EOF | ||||
| 	case io.ErrUnexpectedEOF.Error(): | ||||
| 		return io.ErrUnexpectedEOF | ||||
| 	case errUnexpected.Error(): | ||||
| 		return errUnexpected | ||||
| 	case errDiskFull.Error(): | ||||
| 		return errDiskFull | ||||
| 	case errVolumeNotFound.Error(): | ||||
| 		return errVolumeNotFound | ||||
| 	case errVolumeExists.Error(): | ||||
| 		return errVolumeExists | ||||
| 	case errFileNotFound.Error(): | ||||
| 		return errFileNotFound | ||||
| 	case errFileNameTooLong.Error(): | ||||
| 		return errFileNameTooLong | ||||
| 	case errFileAccessDenied.Error(): | ||||
| 		return errFileAccessDenied | ||||
| 	case errIsNotRegular.Error(): | ||||
| 		return errIsNotRegular | ||||
| 	case errVolumeNotEmpty.Error(): | ||||
| 		return errVolumeNotEmpty | ||||
| 	case errVolumeAccessDenied.Error(): | ||||
| 		return errVolumeAccessDenied | ||||
| 	case errCorruptedFormat.Error(): | ||||
| 		return errCorruptedFormat | ||||
| 	case errUnformattedDisk.Error(): | ||||
| 		return errUnformattedDisk | ||||
| 	case errInvalidAccessKeyID.Error(): | ||||
| 		return errInvalidAccessKeyID | ||||
| 	case errAuthentication.Error(): | ||||
| 		return errAuthentication | ||||
| 	case errRPCAPIVersionUnsupported.Error(): | ||||
| 		return errRPCAPIVersionUnsupported | ||||
| 	case errServerTimeMismatch.Error(): | ||||
| 		return errServerTimeMismatch | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // StorageRPCClient - storage RPC client. | ||||
| type StorageRPCClient struct { | ||||
| 	*RPCClient | ||||
| 	connected bool | ||||
| 	// Plain error of the last RPC call | ||||
| 	lastRPCError error | ||||
| } | ||||
| 
 | ||||
| // Stringer provides a canonicalized representation of network device. | ||||
| func (client *StorageRPCClient) String() string { | ||||
| 	url := client.ServiceURL() | ||||
| 	// Remove the storage RPC path prefix, internal paths are meaningless. why? | ||||
| 	url.Path = strings.TrimPrefix(url.Path, storageServicePath) | ||||
| 	return url.String() | ||||
| } | ||||
| 
 | ||||
| // LastError - returns the last RPC call result, nil or error if any | ||||
| func (client *StorageRPCClient) LastError() error { | ||||
| 	return client.lastRPCError | ||||
| } | ||||
| 
 | ||||
| // Close - closes underneath RPC client. | ||||
| func (client *StorageRPCClient) Close() error { | ||||
| 	client.connected = false | ||||
| 	return toStorageErr(client.RPCClient.Close()) | ||||
| } | ||||
| 
 | ||||
| // IsOnline - returns whether RPC client failed to connect or not. | ||||
| func (client *StorageRPCClient) IsOnline() bool { | ||||
| 	return client.connected | ||||
| } | ||||
| 
 | ||||
| func (client *StorageRPCClient) connect() { | ||||
| 	err := client.Call(storageServiceName+".Connect", &AuthArgs{}, &VoidReply{}) | ||||
| 	client.lastRPCError = err | ||||
| 	client.connected = err == nil | ||||
| } | ||||
| 
 | ||||
| func (client *StorageRPCClient) call(handler string, args interface { | ||||
| 	SetAuthArgs(args AuthArgs) | ||||
| }, reply interface{}) error { | ||||
| 
 | ||||
| 	if !client.connected { | ||||
| 		return errDiskNotFound | ||||
| 	} | ||||
| 
 | ||||
| 	err := client.Call(handler, args, reply) | ||||
| 	client.lastRPCError = err | ||||
| 	if err == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	if isNetworkDisconnectError(err) { | ||||
| 		client.connected = false | ||||
| 	} | ||||
| 
 | ||||
| 	return toStorageErr(err) | ||||
| } | ||||
| 
 | ||||
| // DiskInfo - fetch disk information for a remote disk. | ||||
| func (client *StorageRPCClient) DiskInfo() (info DiskInfo, err error) { | ||||
| 	err = client.call(storageServiceName+".DiskInfo", &AuthArgs{}, &info) | ||||
| 	return info, err | ||||
| } | ||||
| 
 | ||||
| // MakeVol - create a volume on a remote disk. | ||||
| func (client *StorageRPCClient) MakeVol(volume string) (err error) { | ||||
| 	return client.call(storageServiceName+".MakeVol", &VolArgs{Vol: volume}, &VoidReply{}) | ||||
| } | ||||
| 
 | ||||
| // ListVols - List all volumes on a remote disk. | ||||
| func (client *StorageRPCClient) ListVols() ([]VolInfo, error) { | ||||
| 	var reply []VolInfo | ||||
| 	err := client.call(storageServiceName+".ListVols", &AuthArgs{}, &reply) | ||||
| 	return reply, err | ||||
| } | ||||
| 
 | ||||
| // StatVol - get volume info over the network. | ||||
| func (client *StorageRPCClient) StatVol(volume string) (volInfo VolInfo, err error) { | ||||
| 	err = client.call(storageServiceName+".StatVol", &VolArgs{Vol: volume}, &volInfo) | ||||
| 	return volInfo, err | ||||
| } | ||||
| 
 | ||||
| // DeleteVol - Deletes a volume over the network. | ||||
| func (client *StorageRPCClient) DeleteVol(volume string) (err error) { | ||||
| 	return client.call(storageServiceName+".DeleteVol", &VolArgs{Vol: volume}, &VoidReply{}) | ||||
| } | ||||
| 
 | ||||
| // File operations. | ||||
| 
 | ||||
| // PrepareFile - calls PrepareFile RPC. | ||||
| func (client *StorageRPCClient) PrepareFile(volume, path string, length int64) (err error) { | ||||
| 	args := PrepareFileArgs{ | ||||
| 		Vol:  volume, | ||||
| 		Path: path, | ||||
| 		Size: length, | ||||
| 	} | ||||
| 	reply := VoidReply{} | ||||
| 
 | ||||
| 	return client.call(storageServiceName+".PrepareFile", &args, &reply) | ||||
| } | ||||
| 
 | ||||
| // AppendFile - append file writes buffer to a remote network path. | ||||
| func (client *StorageRPCClient) AppendFile(volume, path string, buffer []byte) (err error) { | ||||
| 	args := AppendFileArgs{ | ||||
| 		Vol:    volume, | ||||
| 		Path:   path, | ||||
| 		Buffer: buffer, | ||||
| 	} | ||||
| 	reply := VoidReply{} | ||||
| 
 | ||||
| 	return client.call(storageServiceName+".AppendFile", &args, &reply) | ||||
| } | ||||
| 
 | ||||
| // StatFile - get latest Stat information for a file at path. | ||||
| func (client *StorageRPCClient) StatFile(volume, path string) (fileInfo FileInfo, err error) { | ||||
| 	err = client.call(storageServiceName+".StatFile", &StatFileArgs{Vol: volume, Path: path}, &fileInfo) | ||||
| 	return fileInfo, err | ||||
| } | ||||
| 
 | ||||
| // ReadAll - reads entire contents of the file at path until EOF, returns the | ||||
| // contents in a byte slice. Returns buf == nil if err != nil. | ||||
| // This API is meant to be used on files which have small memory footprint, do | ||||
| // not use this on large files as it would cause server to crash. | ||||
| func (client *StorageRPCClient) ReadAll(volume, path string) (buf []byte, err error) { | ||||
| 	err = client.call(storageServiceName+".ReadAll", &ReadAllArgs{Vol: volume, Path: path}, &buf) | ||||
| 	return buf, err | ||||
| } | ||||
| 
 | ||||
| // ReadFile - reads a file at remote path and fills the buffer. | ||||
| func (client *StorageRPCClient) ReadFile(volume string, path string, offset int64, buffer []byte, verifier *BitrotVerifier) (m int64, err error) { | ||||
| 	// Recover from any panic and return error. | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			err = bytes.ErrTooLarge | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	args := ReadFileArgs{ | ||||
| 		Vol:      volume, | ||||
| 		Path:     path, | ||||
| 		Offset:   offset, | ||||
| 		Length:   int64(len(buffer)), | ||||
| 		Verified: verifier == nil, // Marked accordingly if verifier is set or not. | ||||
| 	} | ||||
| 	if verifier != nil { | ||||
| 		args.Algo = verifier.algorithm | ||||
| 		args.ExpectedHash = verifier.sum | ||||
| 	} | ||||
| 	var reply []byte | ||||
| 
 | ||||
| 	err = client.call(storageServiceName+".ReadFile", &args, &reply) | ||||
| 
 | ||||
| 	// Copy reply to buffer. | ||||
| 	copy(buffer, reply) | ||||
| 
 | ||||
| 	// Return length of result, err if any. | ||||
| 	return int64(len(reply)), err | ||||
| } | ||||
| 
 | ||||
| // ListDir - list all entries at prefix. | ||||
| func (client *StorageRPCClient) ListDir(volume, path string, count int) (entries []string, err error) { | ||||
| 	err = client.call(storageServiceName+".ListDir", &ListDirArgs{Vol: volume, Path: path, Count: count}, &entries) | ||||
| 	return entries, err | ||||
| } | ||||
| 
 | ||||
| // DeleteFile - Delete a file at path. | ||||
| func (client *StorageRPCClient) DeleteFile(volume, path string) (err error) { | ||||
| 	args := DeleteFileArgs{ | ||||
| 		Vol:  volume, | ||||
| 		Path: path, | ||||
| 	} | ||||
| 	reply := VoidReply{} | ||||
| 
 | ||||
| 	return client.call(storageServiceName+".DeleteFile", &args, &reply) | ||||
| } | ||||
| 
 | ||||
| // RenameFile - rename a remote file from source to destination. | ||||
| func (client *StorageRPCClient) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) (err error) { | ||||
| 	args := RenameFileArgs{ | ||||
| 		SrcVol:  srcVolume, | ||||
| 		SrcPath: srcPath, | ||||
| 		DstVol:  dstVolume, | ||||
| 		DstPath: dstPath, | ||||
| 	} | ||||
| 	reply := VoidReply{} | ||||
| 
 | ||||
| 	return client.call(storageServiceName+".RenameFile", &args, &reply) | ||||
| } | ||||
| 
 | ||||
| // NewStorageRPCClient - returns new storage RPC client. | ||||
| func NewStorageRPCClient(host *xnet.Host, endpointPath string) (*StorageRPCClient, error) { | ||||
| 	scheme := "http" | ||||
| 	if globalIsSSL { | ||||
| 		scheme = "https" | ||||
| 	} | ||||
| 
 | ||||
| 	serviceURL := &xnet.URL{ | ||||
| 		Scheme: scheme, | ||||
| 		Host:   host.String(), | ||||
| 		Path:   path.Join(storageServicePath, endpointPath), | ||||
| 	} | ||||
| 
 | ||||
| 	var tlsConfig *tls.Config | ||||
| 	if globalIsSSL { | ||||
| 		tlsConfig = &tls.Config{ | ||||
| 			ServerName: host.Name, | ||||
| 			RootCAs:    globalRootCAs, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	rpcClient, err := NewRPCClient( | ||||
| 		RPCClientArgs{ | ||||
| 			NewAuthTokenFunc: newAuthToken, | ||||
| 			RPCVersion:       globalRPCAPIVersion, | ||||
| 			ServiceName:      storageServiceName, | ||||
| 			ServiceURL:       serviceURL, | ||||
| 			TLSConfig:        tlsConfig, | ||||
| 		}, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &StorageRPCClient{RPCClient: rpcClient}, nil | ||||
| } | ||||
| 
 | ||||
| // Initialize new storage rpc client. | ||||
| func newStorageRPC(endpoint Endpoint) *StorageRPCClient { | ||||
| 	host, err := xnet.ParseHost(endpoint.Host) | ||||
| 	logger.FatalIf(err, "Unable to parse storage RPC Host") | ||||
| 	rpcClient, err := NewStorageRPCClient(host, endpoint.Path) | ||||
| 	logger.FatalIf(err, "Unable to initialize storage RPC client") | ||||
| 	// Attempt first try connection and save error if any. | ||||
| 	rpcClient.connect() | ||||
| 	return rpcClient | ||||
| } | ||||
| @ -1,233 +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 ( | ||||
| 	"io" | ||||
| 	"path" | ||||
| 
 | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/minio/minio/cmd/logger" | ||||
| 	xrpc "github.com/minio/minio/cmd/rpc" | ||||
| ) | ||||
| 
 | ||||
| const storageServiceName = "Storage" | ||||
| const storageServiceSubPath = "/storage" | ||||
| 
 | ||||
| var storageServicePath = path.Join(minioReservedBucketPath, storageServiceSubPath) | ||||
| 
 | ||||
| // storageRPCReceiver - Storage RPC receiver for storage RPC server | ||||
| type storageRPCReceiver struct { | ||||
| 	local *posix | ||||
| } | ||||
| 
 | ||||
| // VolArgs - generic volume args. | ||||
| type VolArgs struct { | ||||
| 	AuthArgs | ||||
| 	Vol string | ||||
| } | ||||
| 
 | ||||
| /// Storage operations handlers. | ||||
| 
 | ||||
| // Connect - authenticates remote connection. | ||||
| func (receiver *storageRPCReceiver) Connect(args *AuthArgs, reply *VoidReply) (err error) { | ||||
| 	return args.Authenticate() | ||||
| } | ||||
| 
 | ||||
| // DiskInfo - disk info handler is rpc wrapper for DiskInfo operation. | ||||
| func (receiver *storageRPCReceiver) DiskInfo(args *AuthArgs, reply *DiskInfo) (err error) { | ||||
| 	*reply, err = receiver.local.DiskInfo() | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| /// Volume operations handlers. | ||||
| 
 | ||||
| // MakeVol - make vol handler is rpc wrapper for MakeVol operation. | ||||
| func (receiver *storageRPCReceiver) MakeVol(args *VolArgs, reply *VoidReply) error { | ||||
| 	return receiver.local.MakeVol(args.Vol) | ||||
| } | ||||
| 
 | ||||
| // ListVols - list vols handler is rpc wrapper for ListVols operation. | ||||
| func (receiver *storageRPCReceiver) ListVols(args *AuthArgs, reply *[]VolInfo) (err error) { | ||||
| 	*reply, err = receiver.local.ListVols() | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // StatVol - stat vol handler is a rpc wrapper for StatVol operation. | ||||
| func (receiver *storageRPCReceiver) StatVol(args *VolArgs, reply *VolInfo) (err error) { | ||||
| 	*reply, err = receiver.local.StatVol(args.Vol) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // DeleteVol - delete vol handler is a rpc wrapper for | ||||
| // DeleteVol operation. | ||||
| func (receiver *storageRPCReceiver) DeleteVol(args *VolArgs, reply *VoidReply) error { | ||||
| 	return receiver.local.DeleteVol(args.Vol) | ||||
| } | ||||
| 
 | ||||
| /// File operations | ||||
| 
 | ||||
| // StatFileArgs represents stat file RPC arguments. | ||||
| type StatFileArgs struct { | ||||
| 	AuthArgs | ||||
| 	Vol  string | ||||
| 	Path string | ||||
| } | ||||
| 
 | ||||
| // StatFile - stat file handler is rpc wrapper to stat file. | ||||
| func (receiver *storageRPCReceiver) StatFile(args *StatFileArgs, reply *FileInfo) (err error) { | ||||
| 	*reply, err = receiver.local.StatFile(args.Vol, args.Path) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // ListDirArgs represents list contents RPC arguments. | ||||
| type ListDirArgs struct { | ||||
| 	AuthArgs | ||||
| 	Vol   string | ||||
| 	Path  string | ||||
| 	Count int | ||||
| } | ||||
| 
 | ||||
| // ListDir - list directory handler is rpc wrapper to list dir. | ||||
| func (receiver *storageRPCReceiver) ListDir(args *ListDirArgs, reply *[]string) (err error) { | ||||
| 	*reply, err = receiver.local.ListDir(args.Vol, args.Path, args.Count) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // ReadAllArgs represents read all RPC arguments. | ||||
| type ReadAllArgs struct { | ||||
| 	AuthArgs | ||||
| 	Vol  string | ||||
| 	Path string | ||||
| } | ||||
| 
 | ||||
| // ReadAll - read all handler is rpc wrapper to read all storage API. | ||||
| func (receiver *storageRPCReceiver) ReadAll(args *ReadAllArgs, reply *[]byte) (err error) { | ||||
| 	*reply, err = receiver.local.ReadAll(args.Vol, args.Path) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // ReadFileArgs represents read file RPC arguments. | ||||
| type ReadFileArgs struct { | ||||
| 	AuthArgs | ||||
| 	Vol          string | ||||
| 	Path         string | ||||
| 	Offset       int64 | ||||
| 	Length       int64 | ||||
| 	Algo         BitrotAlgorithm | ||||
| 	ExpectedHash []byte | ||||
| 	Verified     bool | ||||
| } | ||||
| 
 | ||||
| // ReadFile - read file handler is rpc wrapper to read file. | ||||
| func (receiver *storageRPCReceiver) ReadFile(args *ReadFileArgs, reply *[]byte) error { | ||||
| 	var verifier *BitrotVerifier | ||||
| 	if !args.Verified { | ||||
| 		verifier = NewBitrotVerifier(args.Algo, args.ExpectedHash) | ||||
| 	} | ||||
| 
 | ||||
| 	buf := make([]byte, args.Length) | ||||
| 	n, err := receiver.local.ReadFile(args.Vol, args.Path, args.Offset, buf, verifier) | ||||
| 	// Ignore io.ErrEnexpectedEOF for short reads i.e. less content available than requested. | ||||
| 	if err == io.ErrUnexpectedEOF { | ||||
| 		err = nil | ||||
| 	} | ||||
| 
 | ||||
| 	*reply = buf[0:n] | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // PrepareFileArgs represents append file RPC arguments. | ||||
| type PrepareFileArgs struct { | ||||
| 	AuthArgs | ||||
| 	Vol  string | ||||
| 	Path string | ||||
| 	Size int64 | ||||
| } | ||||
| 
 | ||||
| // PrepareFile - prepare file handler is rpc wrapper to prepare file. | ||||
| func (receiver *storageRPCReceiver) PrepareFile(args *PrepareFileArgs, reply *VoidReply) error { | ||||
| 	return receiver.local.PrepareFile(args.Vol, args.Path, args.Size) | ||||
| } | ||||
| 
 | ||||
| // AppendFileArgs represents append file RPC arguments. | ||||
| type AppendFileArgs struct { | ||||
| 	AuthArgs | ||||
| 	Vol    string | ||||
| 	Path   string | ||||
| 	Buffer []byte | ||||
| } | ||||
| 
 | ||||
| // AppendFile - append file handler is rpc wrapper to append file. | ||||
| func (receiver *storageRPCReceiver) AppendFile(args *AppendFileArgs, reply *VoidReply) error { | ||||
| 	return receiver.local.AppendFile(args.Vol, args.Path, args.Buffer) | ||||
| } | ||||
| 
 | ||||
| // DeleteFileArgs represents delete file RPC arguments. | ||||
| type DeleteFileArgs struct { | ||||
| 	AuthArgs | ||||
| 	Vol  string | ||||
| 	Path string | ||||
| } | ||||
| 
 | ||||
| // DeleteFile - delete file handler is rpc wrapper to delete file. | ||||
| func (receiver *storageRPCReceiver) DeleteFile(args *DeleteFileArgs, reply *VoidReply) error { | ||||
| 	return receiver.local.DeleteFile(args.Vol, args.Path) | ||||
| } | ||||
| 
 | ||||
| // RenameFileArgs represents rename file RPC arguments. | ||||
| type RenameFileArgs struct { | ||||
| 	AuthArgs | ||||
| 	SrcVol  string | ||||
| 	SrcPath string | ||||
| 	DstVol  string | ||||
| 	DstPath string | ||||
| } | ||||
| 
 | ||||
| // RenameFile - rename file handler is rpc wrapper to rename file. | ||||
| func (receiver *storageRPCReceiver) RenameFile(args *RenameFileArgs, reply *VoidReply) error { | ||||
| 	return receiver.local.RenameFile(args.SrcVol, args.SrcPath, args.DstVol, args.DstPath) | ||||
| } | ||||
| 
 | ||||
| // NewStorageRPCServer - returns new storage RPC server. | ||||
| func NewStorageRPCServer(endpointPath string) (*xrpc.Server, error) { | ||||
| 	storage, err := newPosix(endpointPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	rpcServer := xrpc.NewServer() | ||||
| 	if err = rpcServer.RegisterName(storageServiceName, &storageRPCReceiver{storage}); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return rpcServer, nil | ||||
| } | ||||
| 
 | ||||
| // registerStorageRPCRouter - register storage rpc router. | ||||
| func registerStorageRPCRouters(router *mux.Router, endpoints EndpointList) { | ||||
| 	for _, endpoint := range endpoints { | ||||
| 		if endpoint.IsLocal { | ||||
| 			rpcServer, err := NewStorageRPCServer(endpoint.Path) | ||||
| 			if err != nil { | ||||
| 				logger.Fatal(uiErrUnableToWriteInBackend(err), "Unable to configure one of server's RPC services") | ||||
| 			} | ||||
| 			subrouter := router.PathPrefix(minioReservedBucketPath).Subrouter() | ||||
| 			subrouter.Path(path.Join(storageServiceSubPath, endpoint.Path)).HandlerFunc(httpTraceHdrs(rpcServer.ServeHTTP)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										10
									
								
								cmd/utils.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								cmd/utils.go
									
									
									
									
									
								
							| @ -458,3 +458,13 @@ func CloseResponse(respBody io.ReadCloser) { | ||||
| 		respBody.Close() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Used for registering with rest handlers (have a look at registerStorageRESTHandlers for usage example) | ||||
| // If it is passed ["aaaa", "bbbb"], it returns ["aaaa", "{aaaa:.*}", "bbbb", "{bbbb:.*}"] | ||||
| func restQueries(keys ...string) []string { | ||||
| 	var accumulator []string | ||||
| 	for _, key := range keys { | ||||
| 		accumulator = append(accumulator, key, "{"+key+":.*}") | ||||
| 	} | ||||
| 	return accumulator | ||||
| } | ||||
|  | ||||
| @ -470,3 +470,25 @@ func TestIsErrIgnored(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Test queries() | ||||
| func TestQueries(t *testing.T) { | ||||
| 	var testCases = []struct { | ||||
| 		keys      []string | ||||
| 		keyvalues []string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]string{"aaaa", "bbbb"}, | ||||
| 			[]string{"aaaa", "{aaaa:.*}", "bbbb", "{bbbb:.*}"}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for i, test := range testCases { | ||||
| 		keyvalues := restQueries(test.keys...) | ||||
| 		for j := range keyvalues { | ||||
| 			if keyvalues[j] != test.keyvalues[j] { | ||||
| 				t.Fatalf("test %d: keyvalues[%d] does not match", i+1, j) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -194,6 +194,9 @@ func (s *xlSets) connectDisksWithQuorum() { | ||||
| 			s.xlDisks[i][j] = disk | ||||
| 			onlineDisks++ | ||||
| 		} | ||||
| 		// Sleep for a while - so that we don't go into | ||||
| 		// 100% CPU when half the disks are online. | ||||
| 		time.Sleep(500 * time.Millisecond) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user