storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/storage-rest-client.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2018-2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/gob"
    23  	"encoding/hex"
    24  	"errors"
    25  	"io"
    26  	"io/ioutil"
    27  	"net/url"
    28  	"path"
    29  	"strconv"
    30  	"strings"
    31  	"sync"
    32  	"time"
    33  
    34  	xbufio "github.com/philhofer/fwd"
    35  	"github.com/tinylib/msgp/msgp"
    36  
    37  	"storj.io/minio/cmd/http"
    38  	xhttp "storj.io/minio/cmd/http"
    39  	"storj.io/minio/cmd/logger"
    40  	"storj.io/minio/cmd/rest"
    41  	xnet "storj.io/minio/pkg/net"
    42  )
    43  
    44  func isNetworkError(err error) bool {
    45  	if err == nil {
    46  		return false
    47  	}
    48  	if nerr, ok := err.(*rest.NetworkError); ok {
    49  		return xnet.IsNetworkOrHostDown(nerr.Err, false)
    50  	}
    51  	return false
    52  }
    53  
    54  // Converts network error to storageErr. This function is
    55  // written so that the storageAPI errors are consistent
    56  // across network disks.
    57  func toStorageErr(err error) error {
    58  	if err == nil {
    59  		return nil
    60  	}
    61  
    62  	if isNetworkError(err) {
    63  		return errDiskNotFound
    64  	}
    65  
    66  	switch err.Error() {
    67  	case errFaultyDisk.Error():
    68  		return errFaultyDisk
    69  	case errFileCorrupt.Error():
    70  		return errFileCorrupt
    71  	case errUnexpected.Error():
    72  		return errUnexpected
    73  	case errDiskFull.Error():
    74  		return errDiskFull
    75  	case errVolumeNotFound.Error():
    76  		return errVolumeNotFound
    77  	case errVolumeExists.Error():
    78  		return errVolumeExists
    79  	case errFileNotFound.Error():
    80  		return errFileNotFound
    81  	case errFileVersionNotFound.Error():
    82  		return errFileVersionNotFound
    83  	case errFileNameTooLong.Error():
    84  		return errFileNameTooLong
    85  	case errFileAccessDenied.Error():
    86  		return errFileAccessDenied
    87  	case errPathNotFound.Error():
    88  		return errPathNotFound
    89  	case errIsNotRegular.Error():
    90  		return errIsNotRegular
    91  	case errVolumeNotEmpty.Error():
    92  		return errVolumeNotEmpty
    93  	case errVolumeAccessDenied.Error():
    94  		return errVolumeAccessDenied
    95  	case errCorruptedFormat.Error():
    96  		return errCorruptedFormat
    97  	case errUnformattedDisk.Error():
    98  		return errUnformattedDisk
    99  	case errInvalidAccessKeyID.Error():
   100  		return errInvalidAccessKeyID
   101  	case errAuthentication.Error():
   102  		return errAuthentication
   103  	case errRPCAPIVersionUnsupported.Error():
   104  		return errRPCAPIVersionUnsupported
   105  	case errServerTimeMismatch.Error():
   106  		return errServerTimeMismatch
   107  	case io.EOF.Error():
   108  		return io.EOF
   109  	case io.ErrUnexpectedEOF.Error():
   110  		return io.ErrUnexpectedEOF
   111  	case errDiskStale.Error():
   112  		return errDiskNotFound
   113  	case errDiskNotFound.Error():
   114  		return errDiskNotFound
   115  	}
   116  	return err
   117  }
   118  
   119  // Abstracts a remote disk.
   120  type storageRESTClient struct {
   121  	endpoint   Endpoint
   122  	restClient *rest.Client
   123  	diskID     string
   124  
   125  	// Indexes, will be -1 until assigned a set.
   126  	poolIndex, setIndex, diskIndex int
   127  
   128  	diskInfoCache timedValue
   129  	diskHealCache timedValue
   130  }
   131  
   132  // Retrieve location indexes.
   133  func (client *storageRESTClient) GetDiskLoc() (poolIdx, setIdx, diskIdx int) {
   134  	return client.poolIndex, client.setIndex, client.diskIndex
   135  }
   136  
   137  // Set location indexes.
   138  func (client *storageRESTClient) SetDiskLoc(poolIdx, setIdx, diskIdx int) {
   139  	client.poolIndex = poolIdx
   140  	client.setIndex = setIdx
   141  	client.diskIndex = diskIdx
   142  }
   143  
   144  // Wrapper to restClient.Call to handle network errors, in case of network error the connection is makred disconnected
   145  // permanently. The only way to restore the storage connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
   146  // after verifying format.json
   147  func (client *storageRESTClient) call(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (io.ReadCloser, error) {
   148  	if values == nil {
   149  		values = make(url.Values)
   150  	}
   151  	values.Set(storageRESTDiskID, client.diskID)
   152  	respBody, err := client.restClient.Call(ctx, method, values, body, length)
   153  	if err == nil {
   154  		return respBody, nil
   155  	}
   156  
   157  	err = toStorageErr(err)
   158  	return nil, err
   159  }
   160  
   161  // Stringer provides a canonicalized representation of network device.
   162  func (client *storageRESTClient) String() string {
   163  	return client.endpoint.String()
   164  }
   165  
   166  // IsOnline - returns whether RPC client failed to connect or not.
   167  func (client *storageRESTClient) IsOnline() bool {
   168  	return client.restClient.IsOnline()
   169  }
   170  
   171  func (client *storageRESTClient) IsLocal() bool {
   172  	return false
   173  }
   174  
   175  func (client *storageRESTClient) Hostname() string {
   176  	return client.endpoint.Host
   177  }
   178  
   179  func (client *storageRESTClient) Endpoint() Endpoint {
   180  	return client.endpoint
   181  }
   182  
   183  func (client *storageRESTClient) Healing() *healingTracker {
   184  	client.diskHealCache.Once.Do(func() {
   185  		// Update at least every second.
   186  		client.diskHealCache.TTL = time.Second
   187  		client.diskHealCache.Update = func() (interface{}, error) {
   188  			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   189  			defer cancel()
   190  			b, err := client.ReadAll(ctx, minioMetaBucket,
   191  				pathJoin(bucketMetaPrefix, healingTrackerFilename))
   192  			if err != nil {
   193  				// If error, likely not healing.
   194  				return (*healingTracker)(nil), nil
   195  			}
   196  			var h healingTracker
   197  			_, err = h.UnmarshalMsg(b)
   198  			return &h, err
   199  		}
   200  	})
   201  	val, _ := client.diskHealCache.Get()
   202  	return val.(*healingTracker)
   203  }
   204  
   205  func (client *storageRESTClient) NSScanner(ctx context.Context, cache dataUsageCache) (dataUsageCache, error) {
   206  	pr, pw := io.Pipe()
   207  	go func() {
   208  		pw.CloseWithError(cache.serializeTo(pw))
   209  	}()
   210  	respBody, err := client.call(ctx, storageRESTMethodNSScanner, url.Values{}, pr, -1)
   211  	defer http.DrainBody(respBody)
   212  	if err != nil {
   213  		pr.Close()
   214  		return cache, err
   215  	}
   216  	pr.Close()
   217  
   218  	var newCache dataUsageCache
   219  	pr, pw = io.Pipe()
   220  	go func() {
   221  		pw.CloseWithError(waitForHTTPStream(respBody, pw))
   222  	}()
   223  	err = newCache.deserialize(pr)
   224  	pr.CloseWithError(err)
   225  	return newCache, err
   226  }
   227  
   228  func (client *storageRESTClient) GetDiskID() (string, error) {
   229  	// This call should never be over the network, this is always
   230  	// a cached value - caller should make sure to use this
   231  	// function on a fresh disk or make sure to look at the error
   232  	// from a different networked call to validate the GetDiskID()
   233  	return client.diskID, nil
   234  }
   235  
   236  func (client *storageRESTClient) SetDiskID(id string) {
   237  	client.diskID = id
   238  }
   239  
   240  // DiskInfo - fetch disk information for a remote disk.
   241  func (client *storageRESTClient) DiskInfo(ctx context.Context) (info DiskInfo, err error) {
   242  	client.diskInfoCache.Once.Do(func() {
   243  		client.diskInfoCache.TTL = time.Second
   244  		client.diskInfoCache.Update = func() (interface{}, error) {
   245  			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   246  			defer cancel()
   247  			respBody, err := client.call(ctx, storageRESTMethodDiskInfo, nil, nil, -1)
   248  			if err != nil {
   249  				return info, err
   250  			}
   251  			defer http.DrainBody(respBody)
   252  			if err = msgp.Decode(respBody, &info); err != nil {
   253  				return info, err
   254  			}
   255  			if info.Error != "" {
   256  				return info, toStorageErr(errors.New(info.Error))
   257  			}
   258  			return info, nil
   259  		}
   260  	})
   261  	val, err := client.diskInfoCache.Get()
   262  	if err == nil {
   263  		info = val.(DiskInfo)
   264  	}
   265  
   266  	return info, err
   267  }
   268  
   269  // MakeVolBulk - create multiple volumes in a bulk operation.
   270  func (client *storageRESTClient) MakeVolBulk(ctx context.Context, volumes ...string) (err error) {
   271  	values := make(url.Values)
   272  	values.Set(storageRESTVolumes, strings.Join(volumes, ","))
   273  	respBody, err := client.call(ctx, storageRESTMethodMakeVolBulk, values, nil, -1)
   274  	defer http.DrainBody(respBody)
   275  	return err
   276  }
   277  
   278  // MakeVol - create a volume on a remote disk.
   279  func (client *storageRESTClient) MakeVol(ctx context.Context, volume string) (err error) {
   280  	values := make(url.Values)
   281  	values.Set(storageRESTVolume, volume)
   282  	respBody, err := client.call(ctx, storageRESTMethodMakeVol, values, nil, -1)
   283  	defer http.DrainBody(respBody)
   284  	return err
   285  }
   286  
   287  // ListVols - List all volumes on a remote disk.
   288  func (client *storageRESTClient) ListVols(ctx context.Context) (vols []VolInfo, err error) {
   289  	respBody, err := client.call(ctx, storageRESTMethodListVols, nil, nil, -1)
   290  	if err != nil {
   291  		return
   292  	}
   293  	defer http.DrainBody(respBody)
   294  	vinfos := VolsInfo(vols)
   295  	err = msgp.Decode(respBody, &vinfos)
   296  	return vinfos, err
   297  }
   298  
   299  // StatVol - get volume info over the network.
   300  func (client *storageRESTClient) StatVol(ctx context.Context, volume string) (vol VolInfo, err error) {
   301  	values := make(url.Values)
   302  	values.Set(storageRESTVolume, volume)
   303  	respBody, err := client.call(ctx, storageRESTMethodStatVol, values, nil, -1)
   304  	if err != nil {
   305  		return
   306  	}
   307  	defer http.DrainBody(respBody)
   308  	err = msgp.Decode(respBody, &vol)
   309  	return vol, err
   310  }
   311  
   312  // DeleteVol - Deletes a volume over the network.
   313  func (client *storageRESTClient) DeleteVol(ctx context.Context, volume string, forceDelete bool) (err error) {
   314  	values := make(url.Values)
   315  	values.Set(storageRESTVolume, volume)
   316  	if forceDelete {
   317  		values.Set(storageRESTForceDelete, "true")
   318  	}
   319  	respBody, err := client.call(ctx, storageRESTMethodDeleteVol, values, nil, -1)
   320  	defer http.DrainBody(respBody)
   321  	return err
   322  }
   323  
   324  // AppendFile - append to a file.
   325  func (client *storageRESTClient) AppendFile(ctx context.Context, volume string, path string, buf []byte) error {
   326  	values := make(url.Values)
   327  	values.Set(storageRESTVolume, volume)
   328  	values.Set(storageRESTFilePath, path)
   329  	reader := bytes.NewReader(buf)
   330  	respBody, err := client.call(ctx, storageRESTMethodAppendFile, values, reader, -1)
   331  	defer http.DrainBody(respBody)
   332  	return err
   333  }
   334  
   335  func (client *storageRESTClient) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) error {
   336  	values := make(url.Values)
   337  	values.Set(storageRESTVolume, volume)
   338  	values.Set(storageRESTFilePath, path)
   339  	values.Set(storageRESTLength, strconv.Itoa(int(size)))
   340  	respBody, err := client.call(ctx, storageRESTMethodCreateFile, values, ioutil.NopCloser(reader), size)
   341  	defer http.DrainBody(respBody)
   342  	return err
   343  }
   344  
   345  func (client *storageRESTClient) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) error {
   346  	values := make(url.Values)
   347  	values.Set(storageRESTVolume, volume)
   348  	values.Set(storageRESTFilePath, path)
   349  
   350  	var reader bytes.Buffer
   351  	if err := msgp.Encode(&reader, &fi); err != nil {
   352  		return err
   353  	}
   354  
   355  	respBody, err := client.call(ctx, storageRESTMethodWriteMetadata, values, &reader, -1)
   356  	defer http.DrainBody(respBody)
   357  	return err
   358  }
   359  
   360  func (client *storageRESTClient) UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo) error {
   361  	values := make(url.Values)
   362  	values.Set(storageRESTVolume, volume)
   363  	values.Set(storageRESTFilePath, path)
   364  
   365  	var reader bytes.Buffer
   366  	if err := msgp.Encode(&reader, &fi); err != nil {
   367  		return err
   368  	}
   369  
   370  	respBody, err := client.call(ctx, storageRESTMethodUpdateMetadata, values, &reader, -1)
   371  	defer http.DrainBody(respBody)
   372  	return err
   373  }
   374  
   375  func (client *storageRESTClient) DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool) error {
   376  	values := make(url.Values)
   377  	values.Set(storageRESTVolume, volume)
   378  	values.Set(storageRESTFilePath, path)
   379  	values.Set(storageRESTForceDelMarker, strconv.FormatBool(forceDelMarker))
   380  
   381  	var buffer bytes.Buffer
   382  	if err := msgp.Encode(&buffer, &fi); err != nil {
   383  		return err
   384  	}
   385  
   386  	respBody, err := client.call(ctx, storageRESTMethodDeleteVersion, values, &buffer, -1)
   387  	defer http.DrainBody(respBody)
   388  	return err
   389  }
   390  
   391  // WriteAll - write all data to a file.
   392  func (client *storageRESTClient) WriteAll(ctx context.Context, volume string, path string, b []byte) error {
   393  	values := make(url.Values)
   394  	values.Set(storageRESTVolume, volume)
   395  	values.Set(storageRESTFilePath, path)
   396  	respBody, err := client.call(ctx, storageRESTMethodWriteAll, values, bytes.NewBuffer(b), int64(len(b)))
   397  	defer http.DrainBody(respBody)
   398  	return err
   399  }
   400  
   401  // CheckFile - stat a file metadata.
   402  func (client *storageRESTClient) CheckFile(ctx context.Context, volume string, path string) error {
   403  	values := make(url.Values)
   404  	values.Set(storageRESTVolume, volume)
   405  	values.Set(storageRESTFilePath, path)
   406  	respBody, err := client.call(ctx, storageRESTMethodCheckFile, values, nil, -1)
   407  	defer http.DrainBody(respBody)
   408  	return err
   409  }
   410  
   411  // CheckParts - stat all file parts.
   412  func (client *storageRESTClient) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) error {
   413  	values := make(url.Values)
   414  	values.Set(storageRESTVolume, volume)
   415  	values.Set(storageRESTFilePath, path)
   416  
   417  	var reader bytes.Buffer
   418  	if err := msgp.Encode(&reader, &fi); err != nil {
   419  		logger.LogIf(context.Background(), err)
   420  		return err
   421  	}
   422  
   423  	respBody, err := client.call(ctx, storageRESTMethodCheckParts, values, &reader, -1)
   424  	defer http.DrainBody(respBody)
   425  	return err
   426  }
   427  
   428  // RenameData - rename source path to destination path atomically, metadata and data file.
   429  func (client *storageRESTClient) RenameData(ctx context.Context, srcVolume, srcPath string, fi FileInfo, dstVolume, dstPath string) (err error) {
   430  	values := make(url.Values)
   431  	values.Set(storageRESTSrcVolume, srcVolume)
   432  	values.Set(storageRESTSrcPath, srcPath)
   433  	values.Set(storageRESTDstVolume, dstVolume)
   434  	values.Set(storageRESTDstPath, dstPath)
   435  
   436  	var reader bytes.Buffer
   437  	if err = msgp.Encode(&reader, &fi); err != nil {
   438  		return err
   439  	}
   440  
   441  	respBody, err := client.call(ctx, storageRESTMethodRenameData, values, &reader, -1)
   442  	defer http.DrainBody(respBody)
   443  
   444  	return err
   445  }
   446  
   447  // where we keep old *Readers
   448  var readMsgpReaderPool = sync.Pool{New: func() interface{} { return &msgp.Reader{} }}
   449  
   450  // mspNewReader returns a *Reader that reads from the provided reader.
   451  // The reader will be buffered.
   452  func msgpNewReader(r io.Reader) *msgp.Reader {
   453  	p := readMsgpReaderPool.Get().(*msgp.Reader)
   454  	if p.R == nil {
   455  		p.R = xbufio.NewReaderSize(r, 8<<10)
   456  	} else {
   457  		p.R.Reset(r)
   458  	}
   459  	return p
   460  }
   461  
   462  func (client *storageRESTClient) ReadVersion(ctx context.Context, volume, path, versionID string, readData bool) (fi FileInfo, err error) {
   463  	values := make(url.Values)
   464  	values.Set(storageRESTVolume, volume)
   465  	values.Set(storageRESTFilePath, path)
   466  	values.Set(storageRESTVersionID, versionID)
   467  	values.Set(storageRESTReadData, strconv.FormatBool(readData))
   468  
   469  	respBody, err := client.call(ctx, storageRESTMethodReadVersion, values, nil, -1)
   470  	if err != nil {
   471  		return fi, err
   472  	}
   473  	defer http.DrainBody(respBody)
   474  
   475  	dec := msgpNewReader(respBody)
   476  	defer readMsgpReaderPool.Put(dec)
   477  
   478  	err = fi.DecodeMsg(dec)
   479  	return fi, err
   480  }
   481  
   482  // ReadAll - reads all contents of a file.
   483  func (client *storageRESTClient) ReadAll(ctx context.Context, volume string, path string) ([]byte, error) {
   484  	values := make(url.Values)
   485  	values.Set(storageRESTVolume, volume)
   486  	values.Set(storageRESTFilePath, path)
   487  	respBody, err := client.call(ctx, storageRESTMethodReadAll, values, nil, -1)
   488  	if err != nil {
   489  		return nil, err
   490  	}
   491  	defer http.DrainBody(respBody)
   492  	return ioutil.ReadAll(respBody)
   493  }
   494  
   495  // ReadFileStream - returns a reader for the requested file.
   496  func (client *storageRESTClient) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) {
   497  	values := make(url.Values)
   498  	values.Set(storageRESTVolume, volume)
   499  	values.Set(storageRESTFilePath, path)
   500  	values.Set(storageRESTOffset, strconv.Itoa(int(offset)))
   501  	values.Set(storageRESTLength, strconv.Itoa(int(length)))
   502  	respBody, err := client.call(ctx, storageRESTMethodReadFileStream, values, nil, -1)
   503  	if err != nil {
   504  		return nil, err
   505  	}
   506  	return respBody, nil
   507  }
   508  
   509  // ReadFile - reads section of a file.
   510  func (client *storageRESTClient) ReadFile(ctx context.Context, volume string, path string, offset int64, buf []byte, verifier *BitrotVerifier) (int64, error) {
   511  	values := make(url.Values)
   512  	values.Set(storageRESTVolume, volume)
   513  	values.Set(storageRESTFilePath, path)
   514  	values.Set(storageRESTOffset, strconv.Itoa(int(offset)))
   515  	values.Set(storageRESTLength, strconv.Itoa(len(buf)))
   516  	if verifier != nil {
   517  		values.Set(storageRESTBitrotAlgo, verifier.algorithm.String())
   518  		values.Set(storageRESTBitrotHash, hex.EncodeToString(verifier.sum))
   519  	} else {
   520  		values.Set(storageRESTBitrotAlgo, "")
   521  		values.Set(storageRESTBitrotHash, "")
   522  	}
   523  	respBody, err := client.call(ctx, storageRESTMethodReadFile, values, nil, -1)
   524  	if err != nil {
   525  		return 0, err
   526  	}
   527  	defer http.DrainBody(respBody)
   528  	n, err := io.ReadFull(respBody, buf)
   529  	return int64(n), err
   530  }
   531  
   532  // ListDir - lists a directory.
   533  func (client *storageRESTClient) ListDir(ctx context.Context, volume, dirPath string, count int) (entries []string, err error) {
   534  	values := make(url.Values)
   535  	values.Set(storageRESTVolume, volume)
   536  	values.Set(storageRESTDirPath, dirPath)
   537  	values.Set(storageRESTCount, strconv.Itoa(count))
   538  	respBody, err := client.call(ctx, storageRESTMethodListDir, values, nil, -1)
   539  	if err != nil {
   540  		return nil, err
   541  	}
   542  	defer http.DrainBody(respBody)
   543  	err = gob.NewDecoder(respBody).Decode(&entries)
   544  	return entries, err
   545  }
   546  
   547  // DeleteFile - deletes a file.
   548  func (client *storageRESTClient) Delete(ctx context.Context, volume string, path string, recursive bool) error {
   549  	values := make(url.Values)
   550  	values.Set(storageRESTVolume, volume)
   551  	values.Set(storageRESTFilePath, path)
   552  	values.Set(storageRESTRecursive, strconv.FormatBool(recursive))
   553  
   554  	respBody, err := client.call(ctx, storageRESTMethodDeleteFile, values, nil, -1)
   555  	defer http.DrainBody(respBody)
   556  	return err
   557  }
   558  
   559  // DeleteVersions - deletes list of specified versions if present
   560  func (client *storageRESTClient) DeleteVersions(ctx context.Context, volume string, versions []FileInfo) (errs []error) {
   561  	if len(versions) == 0 {
   562  		return errs
   563  	}
   564  
   565  	values := make(url.Values)
   566  	values.Set(storageRESTVolume, volume)
   567  	values.Set(storageRESTTotalVersions, strconv.Itoa(len(versions)))
   568  
   569  	var buffer bytes.Buffer
   570  	encoder := msgp.NewWriter(&buffer)
   571  	for _, version := range versions {
   572  		version.EncodeMsg(encoder)
   573  	}
   574  	logger.LogIf(ctx, encoder.Flush())
   575  
   576  	errs = make([]error, len(versions))
   577  
   578  	respBody, err := client.call(ctx, storageRESTMethodDeleteVersions, values, &buffer, -1)
   579  	defer http.DrainBody(respBody)
   580  	if err != nil {
   581  		for i := range errs {
   582  			errs[i] = err
   583  		}
   584  		return errs
   585  	}
   586  
   587  	reader, err := waitForHTTPResponse(respBody)
   588  	if err != nil {
   589  		for i := range errs {
   590  			errs[i] = err
   591  		}
   592  		return errs
   593  	}
   594  
   595  	dErrResp := &DeleteVersionsErrsResp{}
   596  	if err = gob.NewDecoder(reader).Decode(dErrResp); err != nil {
   597  		for i := range errs {
   598  			errs[i] = err
   599  		}
   600  		return errs
   601  	}
   602  
   603  	for i, dErr := range dErrResp.Errs {
   604  		errs[i] = toStorageErr(dErr)
   605  	}
   606  
   607  	return errs
   608  }
   609  
   610  // RenameFile - renames a file.
   611  func (client *storageRESTClient) RenameFile(ctx context.Context, srcVolume, srcPath, dstVolume, dstPath string) (err error) {
   612  	values := make(url.Values)
   613  	values.Set(storageRESTSrcVolume, srcVolume)
   614  	values.Set(storageRESTSrcPath, srcPath)
   615  	values.Set(storageRESTDstVolume, dstVolume)
   616  	values.Set(storageRESTDstPath, dstPath)
   617  	respBody, err := client.call(ctx, storageRESTMethodRenameFile, values, nil, -1)
   618  	defer http.DrainBody(respBody)
   619  	return err
   620  }
   621  
   622  func (client *storageRESTClient) VerifyFile(ctx context.Context, volume, path string, fi FileInfo) error {
   623  	values := make(url.Values)
   624  	values.Set(storageRESTVolume, volume)
   625  	values.Set(storageRESTFilePath, path)
   626  
   627  	var reader bytes.Buffer
   628  	if err := msgp.Encode(&reader, &fi); err != nil {
   629  		return err
   630  	}
   631  
   632  	respBody, err := client.call(ctx, storageRESTMethodVerifyFile, values, &reader, -1)
   633  	defer http.DrainBody(respBody)
   634  	if err != nil {
   635  		return err
   636  	}
   637  
   638  	respReader, err := waitForHTTPResponse(respBody)
   639  	if err != nil {
   640  		return err
   641  	}
   642  
   643  	verifyResp := &VerifyFileResp{}
   644  	if err = gob.NewDecoder(respReader).Decode(verifyResp); err != nil {
   645  		return err
   646  	}
   647  
   648  	return toStorageErr(verifyResp.Err)
   649  }
   650  
   651  // Close - marks the client as closed.
   652  func (client *storageRESTClient) Close() error {
   653  	client.restClient.Close()
   654  	return nil
   655  }
   656  
   657  // Returns a storage rest client.
   658  func newStorageRESTClient(endpoint Endpoint, healthcheck bool) *storageRESTClient {
   659  	serverURL := &url.URL{
   660  		Scheme: endpoint.Scheme,
   661  		Host:   endpoint.Host,
   662  		Path:   path.Join(storageRESTPrefix, endpoint.Path, storageRESTVersion),
   663  	}
   664  
   665  	restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
   666  
   667  	if healthcheck {
   668  		// Use a separate client to avoid recursive calls.
   669  		healthClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
   670  		healthClient.ExpectTimeouts = true
   671  		restClient.HealthCheckFn = func() bool {
   672  			ctx, cancel := context.WithTimeout(context.Background(), restClient.HealthCheckTimeout)
   673  			defer cancel()
   674  			respBody, err := healthClient.Call(ctx, storageRESTMethodHealth, nil, nil, -1)
   675  			xhttp.DrainBody(respBody)
   676  			return toStorageErr(err) != errDiskNotFound
   677  		}
   678  	}
   679  
   680  	return &storageRESTClient{endpoint: endpoint, restClient: restClient, poolIndex: -1, setIndex: -1, diskIndex: -1}
   681  }