Move remote disk StorageAPI abstraction from RPC to REST (#6464)
parent
670f9788e3
commit
81bee93b8d
@ -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, |
||||
} |
||||
} |
@ -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} |
||||
} |
@ -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" |
||||
) |
@ -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)...) |
||||
|
||||
} |
||||
} |
@ -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)) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue