github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/dsync/dsync-client_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package dsync
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"errors"
    24  	"net/http"
    25  	"net/url"
    26  	"time"
    27  
    28  	xhttp "github.com/minio/minio/internal/http"
    29  	"github.com/minio/minio/internal/rest"
    30  )
    31  
    32  // ReconnectRESTClient is a wrapper type for rest.Client which provides reconnect on first failure.
    33  type ReconnectRESTClient struct {
    34  	u    *url.URL
    35  	rest *rest.Client
    36  }
    37  
    38  // newClient constructs a ReconnectRESTClient object with addr and endpoint initialized.
    39  // It _doesn't_ connect to the remote endpoint. See Call method to see when the
    40  // connect happens.
    41  func newClient(endpoint string) NetLocker {
    42  	u, err := url.Parse(endpoint)
    43  	if err != nil {
    44  		panic(err)
    45  	}
    46  
    47  	tr := &http.Transport{
    48  		Proxy:                 http.ProxyFromEnvironment,
    49  		MaxIdleConnsPerHost:   1024,
    50  		WriteBufferSize:       32 << 10, // 32KiB moving up from 4KiB default
    51  		ReadBufferSize:        32 << 10, // 32KiB moving up from 4KiB default
    52  		IdleConnTimeout:       15 * time.Second,
    53  		ResponseHeaderTimeout: 15 * time.Minute, // Set conservative timeouts for MinIO internode.
    54  		TLSHandshakeTimeout:   15 * time.Second,
    55  		ExpectContinueTimeout: 15 * time.Second,
    56  		// Go net/http automatically unzip if content-type is
    57  		// gzip disable this feature, as we are always interested
    58  		// in raw stream.
    59  		DisableCompression: true,
    60  	}
    61  
    62  	return &ReconnectRESTClient{
    63  		u:    u,
    64  		rest: rest.NewClient(u, tr, nil),
    65  	}
    66  }
    67  
    68  // Close closes the underlying socket file descriptor.
    69  func (restClient *ReconnectRESTClient) IsOnline() bool {
    70  	// If rest client has not connected yet there is nothing to close.
    71  	return restClient.rest != nil
    72  }
    73  
    74  func (restClient *ReconnectRESTClient) IsLocal() bool {
    75  	return false
    76  }
    77  
    78  // Close closes the underlying socket file descriptor.
    79  func (restClient *ReconnectRESTClient) Close() error {
    80  	return nil
    81  }
    82  
    83  var (
    84  	errLockConflict = errors.New("lock conflict")
    85  	errLockNotFound = errors.New("lock not found")
    86  )
    87  
    88  func toLockError(err error) error {
    89  	if err == nil {
    90  		return nil
    91  	}
    92  
    93  	switch err.Error() {
    94  	case errLockConflict.Error():
    95  		return errLockConflict
    96  	case errLockNotFound.Error():
    97  		return errLockNotFound
    98  	}
    99  	return err
   100  }
   101  
   102  // Call makes a REST call to the remote endpoint using the msgp codec
   103  func (restClient *ReconnectRESTClient) Call(method string, args LockArgs) (status bool, err error) {
   104  	buf, err := args.MarshalMsg(nil)
   105  	if err != nil {
   106  		return false, err
   107  	}
   108  	body := bytes.NewReader(buf)
   109  	respBody, err := restClient.rest.Call(context.Background(), method,
   110  		url.Values{}, body, body.Size())
   111  	defer xhttp.DrainBody(respBody)
   112  
   113  	switch toLockError(err) {
   114  	case nil:
   115  		return true, nil
   116  	case errLockConflict, errLockNotFound:
   117  		return false, nil
   118  	default:
   119  		return false, err
   120  	}
   121  }
   122  
   123  func (restClient *ReconnectRESTClient) RLock(ctx context.Context, args LockArgs) (status bool, err error) {
   124  	return restClient.Call("/v1/rlock", args)
   125  }
   126  
   127  func (restClient *ReconnectRESTClient) Lock(ctx context.Context, args LockArgs) (status bool, err error) {
   128  	return restClient.Call("/v1/lock", args)
   129  }
   130  
   131  func (restClient *ReconnectRESTClient) RUnlock(ctx context.Context, args LockArgs) (status bool, err error) {
   132  	return restClient.Call("/v1/runlock", args)
   133  }
   134  
   135  func (restClient *ReconnectRESTClient) Unlock(ctx context.Context, args LockArgs) (status bool, err error) {
   136  	return restClient.Call("/v1/unlock", args)
   137  }
   138  
   139  func (restClient *ReconnectRESTClient) Refresh(ctx context.Context, args LockArgs) (refreshed bool, err error) {
   140  	return restClient.Call("/v1/refresh", args)
   141  }
   142  
   143  func (restClient *ReconnectRESTClient) ForceUnlock(ctx context.Context, args LockArgs) (reply bool, err error) {
   144  	return restClient.Call("/v1/force-unlock", args)
   145  }
   146  
   147  func (restClient *ReconnectRESTClient) String() string {
   148  	return restClient.u.String()
   149  }