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

     1  /*
     2   * MinIO Cloud Storage, (C) 2019 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  	"io"
    23  	"net/url"
    24  	"strconv"
    25  
    26  	"storj.io/minio/cmd/http"
    27  	xhttp "storj.io/minio/cmd/http"
    28  	"storj.io/minio/cmd/rest"
    29  	"storj.io/minio/pkg/dsync"
    30  )
    31  
    32  // lockRESTClient is authenticable lock REST client
    33  type lockRESTClient struct {
    34  	restClient *rest.Client
    35  	u          *url.URL
    36  }
    37  
    38  func toLockError(err error) error {
    39  	if err == nil {
    40  		return nil
    41  	}
    42  
    43  	switch err.Error() {
    44  	case errLockConflict.Error():
    45  		return errLockConflict
    46  	case errLockNotFound.Error():
    47  		return errLockNotFound
    48  	}
    49  	return err
    50  }
    51  
    52  // String stringer *dsync.NetLocker* interface compatible method.
    53  func (client *lockRESTClient) String() string {
    54  	return client.u.String()
    55  }
    56  
    57  // Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
    58  // permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
    59  // after verifying format.json
    60  func (client *lockRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
    61  	if values == nil {
    62  		values = make(url.Values)
    63  	}
    64  
    65  	respBody, err = client.restClient.Call(ctx, method, values, body, length)
    66  	if err == nil {
    67  		return respBody, nil
    68  	}
    69  
    70  	return nil, toLockError(err)
    71  }
    72  
    73  // IsOnline - returns whether REST client failed to connect or not.
    74  func (client *lockRESTClient) IsOnline() bool {
    75  	return client.restClient.IsOnline()
    76  }
    77  
    78  // Not a local locker
    79  func (client *lockRESTClient) IsLocal() bool {
    80  	return false
    81  }
    82  
    83  // Close - marks the client as closed.
    84  func (client *lockRESTClient) Close() error {
    85  	client.restClient.Close()
    86  	return nil
    87  }
    88  
    89  // restCall makes a call to the lock REST server.
    90  func (client *lockRESTClient) restCall(ctx context.Context, call string, args dsync.LockArgs) (reply bool, err error) {
    91  	values := url.Values{}
    92  	values.Set(lockRESTUID, args.UID)
    93  	values.Set(lockRESTOwner, args.Owner)
    94  	values.Set(lockRESTSource, args.Source)
    95  	values.Set(lockRESTQuorum, strconv.Itoa(args.Quorum))
    96  	var buffer bytes.Buffer
    97  	for _, resource := range args.Resources {
    98  		buffer.WriteString(resource)
    99  		buffer.WriteString("\n")
   100  	}
   101  	respBody, err := client.callWithContext(ctx, call, values, &buffer, -1)
   102  	defer http.DrainBody(respBody)
   103  	switch err {
   104  	case nil:
   105  		return true, nil
   106  	case errLockConflict, errLockNotFound:
   107  		return false, nil
   108  	default:
   109  		return false, err
   110  	}
   111  }
   112  
   113  // RLock calls read lock REST API.
   114  func (client *lockRESTClient) RLock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
   115  	return client.restCall(ctx, lockRESTMethodRLock, args)
   116  }
   117  
   118  // Lock calls lock REST API.
   119  func (client *lockRESTClient) Lock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
   120  	return client.restCall(ctx, lockRESTMethodLock, args)
   121  }
   122  
   123  // RUnlock calls read unlock REST API.
   124  func (client *lockRESTClient) RUnlock(args dsync.LockArgs) (reply bool, err error) {
   125  	return client.restCall(context.Background(), lockRESTMethodRUnlock, args)
   126  }
   127  
   128  // RUnlock calls read unlock REST API.
   129  func (client *lockRESTClient) Refresh(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
   130  	return client.restCall(ctx, lockRESTMethodRefresh, args)
   131  }
   132  
   133  // Unlock calls write unlock RPC.
   134  func (client *lockRESTClient) Unlock(args dsync.LockArgs) (reply bool, err error) {
   135  	return client.restCall(context.Background(), lockRESTMethodUnlock, args)
   136  }
   137  
   138  // ForceUnlock calls force unlock handler to forcibly unlock an active lock.
   139  func (client *lockRESTClient) ForceUnlock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) {
   140  	return client.restCall(ctx, lockRESTMethodForceUnlock, args)
   141  }
   142  
   143  func newLockAPI(endpoint Endpoint) dsync.NetLocker {
   144  	if endpoint.IsLocal {
   145  		return globalLockServer
   146  	}
   147  	return newlockRESTClient(endpoint)
   148  }
   149  
   150  // Returns a lock rest client.
   151  func newlockRESTClient(endpoint Endpoint) *lockRESTClient {
   152  	serverURL := &url.URL{
   153  		Scheme: endpoint.Scheme,
   154  		Host:   endpoint.Host,
   155  		Path:   pathJoin(lockRESTPrefix, lockRESTVersion),
   156  	}
   157  
   158  	restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
   159  	restClient.ExpectTimeouts = true
   160  	// Use a separate client to avoid recursive calls.
   161  	healthClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken)
   162  	healthClient.ExpectTimeouts = true
   163  	restClient.HealthCheckFn = func() bool {
   164  		ctx, cancel := context.WithTimeout(context.Background(), restClient.HealthCheckTimeout)
   165  		defer cancel()
   166  		respBody, err := healthClient.Call(ctx, lockRESTMethodHealth, nil, nil, -1)
   167  		xhttp.DrainBody(respBody)
   168  		return !isNetworkError(err)
   169  	}
   170  
   171  	return &lockRESTClient{u: &url.URL{
   172  		Scheme: endpoint.Scheme,
   173  		Host:   endpoint.Host,
   174  	}, restClient: restClient}
   175  }