mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05: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 newPosix(endpoint.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newStorageRPC(endpoint), nil
|
return newStorageRESTClient(endpoint), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup a directory recursively.
|
// 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.
|
// Composed function registering routers for only distributed XL setup.
|
||||||
func registerDistXLRouters(router *mux.Router, endpoints EndpointList) {
|
func registerDistXLRouters(router *mux.Router, endpoints EndpointList) {
|
||||||
// Register storage rpc router only if its a distributed setup.
|
// Register storage rpc router only if its a distributed setup.
|
||||||
registerStorageRPCRouters(router, endpoints)
|
registerStorageRESTHandlers(router, endpoints)
|
||||||
|
|
||||||
// Register distributed namespace lock.
|
// Register distributed namespace lock.
|
||||||
registerDistNSLockRouter(router)
|
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 (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
xnet "github.com/minio/minio/pkg/net"
|
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.
|
// 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) {
|
func newStorageRESTHTTPServerClient(t *testing.T) (*httptest.Server, *storageRESTClient, *serverConfig, string) {
|
||||||
endpointPath, err := ioutil.TempDir("", ".TestStorageRPC.")
|
endpointPath, err := ioutil.TempDir("", ".TestStorageREST.")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %v", err)
|
t.Fatalf("unexpected error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcServer, err := NewStorageRPCServer(endpointPath)
|
router := mux.NewRouter()
|
||||||
if err != nil {
|
httpServer := httptest.NewServer(router)
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
rpcServer.ServeHTTP(w, r)
|
|
||||||
}))
|
|
||||||
|
|
||||||
url, err := xnet.ParseURL(httpServer.URL)
|
url, err := xnet.ParseURL(httpServer.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %v", err)
|
t.Fatalf("unexpected error %v", err)
|
||||||
}
|
}
|
||||||
|
url.Path = endpointPath
|
||||||
|
|
||||||
host, err := xnet.ParseHost(url.Host)
|
endpoint, err := NewEndpoint(url.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %v", err)
|
t.Fatalf("NewEndpoint failed %v", endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerStorageRESTHandlers(router, EndpointList{endpoint})
|
||||||
|
restClient := newStorageRESTClient(endpoint)
|
||||||
|
|
||||||
prevGlobalServerConfig := globalServerConfig
|
prevGlobalServerConfig := globalServerConfig
|
||||||
globalServerConfig = newServerConfig()
|
globalServerConfig = newServerConfig()
|
||||||
|
|
||||||
rpcClient, err := NewStorageRPCClient(host, endpointPath)
|
return httpServer, restClient, prevGlobalServerConfig, endpointPath
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
rpcClient.connected = true
|
|
||||||
|
|
||||||
return httpServer, rpcClient, prevGlobalServerConfig, endpointPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientDiskInfo(t *testing.T) {
|
func TestStorageRESTClientDiskInfo(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIDiskInfo(t, rpcClient)
|
testStorageAPIDiskInfo(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientMakeVol(t *testing.T) {
|
func TestStorageRESTClientMakeVol(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIMakeVol(t, rpcClient)
|
testStorageAPIMakeVol(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientListVols(t *testing.T) {
|
func TestStorageRESTClientListVols(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIListVols(t, rpcClient)
|
testStorageAPIListVols(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientStatVol(t *testing.T) {
|
func TestStorageRESTClientStatVol(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIStatVol(t, rpcClient)
|
testStorageAPIStatVol(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientDeleteVol(t *testing.T) {
|
func TestStorageRESTClientDeleteVol(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIDeleteVol(t, rpcClient)
|
testStorageAPIDeleteVol(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientStatFile(t *testing.T) {
|
func TestStorageRESTClientStatFile(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIStatFile(t, rpcClient)
|
testStorageAPIStatFile(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientListDir(t *testing.T) {
|
func TestStorageRESTClientListDir(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIListDir(t, rpcClient)
|
testStorageAPIListDir(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientReadAll(t *testing.T) {
|
func TestStorageRESTClientReadAll(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIReadAll(t, rpcClient)
|
testStorageAPIReadAll(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientReadFile(t *testing.T) {
|
func TestStorageRESTClientReadFile(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIReadFile(t, rpcClient)
|
testStorageAPIReadFile(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientPrepareFile(t *testing.T) {
|
func TestStorageRESTClientPrepareFile(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIPrepareFile(t, rpcClient)
|
testStorageAPIPrepareFile(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientAppendFile(t *testing.T) {
|
func TestStorageRESTClientAppendFile(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIAppendFile(t, rpcClient)
|
testStorageAPIAppendFile(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientDeleteFile(t *testing.T) {
|
func TestStorageRESTClientDeleteFile(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
defer os.RemoveAll(endpointPath)
|
||||||
|
|
||||||
testStorageAPIDeleteFile(t, rpcClient)
|
testStorageAPIDeleteFile(t, restClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStorageRPCClientRenameFile(t *testing.T) {
|
func TestStorageRESTClientRenameFile(t *testing.T) {
|
||||||
httpServer, rpcClient, prevGlobalServerConfig, endpointPath := newStorageRPCHTTPServerClient(t)
|
httpServer, restClient, prevGlobalServerConfig, endpointPath := newStorageRESTHTTPServerClient(t)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
defer func() {
|
defer func() {
|
||||||
globalServerConfig = prevGlobalServerConfig
|
globalServerConfig = prevGlobalServerConfig
|
||||||
}()
|
}()
|
||||||
defer os.RemoveAll(endpointPath)
|
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()
|
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
|
s.xlDisks[i][j] = disk
|
||||||
onlineDisks++
|
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…
Reference in New Issue
Block a user