| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |  * MinIO Cloud Storage, (C) 2019 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 ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"crypto/tls" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2019-10-09 14:11:15 +08:00
										 |  |  | 	"sync/atomic" | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/minio/minio/cmd/http" | 
					
						
							|  |  |  | 	"github.com/minio/minio/cmd/logger" | 
					
						
							|  |  |  | 	"github.com/minio/minio/cmd/rest" | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/dsync" | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // lockRESTClient is authenticable lock REST client
 | 
					
						
							|  |  |  | type lockRESTClient struct { | 
					
						
							|  |  |  | 	restClient *rest.Client | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 	endpoint   Endpoint | 
					
						
							| 
									
										
										
										
											2019-10-09 14:11:15 +08:00
										 |  |  | 	connected  int32 | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 07:12:57 +08:00
										 |  |  | func toLockError(err error) error { | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch err.Error() { | 
					
						
							|  |  |  | 	case errLockConflict.Error(): | 
					
						
							|  |  |  | 		return errLockConflict | 
					
						
							| 
									
										
										
										
											2019-11-26 08:39:43 +08:00
										 |  |  | 	case errLockNotExpired.Error(): | 
					
						
							|  |  |  | 		return errLockNotExpired | 
					
						
							| 
									
										
										
										
											2019-08-29 07:12:57 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | // String stringer *dsync.NetLocker* interface compatible method.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) String() string { | 
					
						
							|  |  |  | 	return client.endpoint.String() | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
 | 
					
						
							|  |  |  | // permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
 | 
					
						
							|  |  |  | // after verifying format.json
 | 
					
						
							|  |  |  | func (client *lockRESTClient) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) { | 
					
						
							| 
									
										
										
										
											2019-10-09 14:11:15 +08:00
										 |  |  | 	if !client.IsOnline() { | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 		return nil, errors.New("Lock rest server node is down") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if values == nil { | 
					
						
							|  |  |  | 		values = make(url.Values) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	respBody, err = client.restClient.Call(method, values, body, length) | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		return respBody, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if isNetworkError(err) { | 
					
						
							| 
									
										
										
										
											2020-01-10 18:35:06 +08:00
										 |  |  | 		time.AfterFunc(defaultRetryUnit, func() { | 
					
						
							|  |  |  | 			// After 1 seconds, take this lock client online for a retry.
 | 
					
						
							| 
									
										
										
										
											2019-10-09 14:11:15 +08:00
										 |  |  | 			atomic.StoreInt32(&client.connected, 1) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-09 14:11:15 +08:00
										 |  |  | 		atomic.StoreInt32(&client.connected, 0) | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 07:12:57 +08:00
										 |  |  | 	return nil, toLockError(err) | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsOnline - returns whether REST client failed to connect or not.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) IsOnline() bool { | 
					
						
							| 
									
										
										
										
											2019-10-09 14:11:15 +08:00
										 |  |  | 	return atomic.LoadInt32(&client.connected) == 1 | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Close - marks the client as closed.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) Close() error { | 
					
						
							| 
									
										
										
										
											2019-10-09 14:11:15 +08:00
										 |  |  | 	atomic.StoreInt32(&client.connected, 0) | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 	client.restClient.Close() | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // restCall makes a call to the lock REST server.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) restCall(call string, args dsync.LockArgs) (reply bool, err error) { | 
					
						
							| 
									
										
										
										
											2019-08-06 02:45:30 +08:00
										 |  |  | 	values := url.Values{} | 
					
						
							|  |  |  | 	values.Set(lockRESTUID, args.UID) | 
					
						
							|  |  |  | 	values.Set(lockRESTSource, args.Source) | 
					
						
							|  |  |  | 	values.Set(lockRESTResource, args.Resource) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	respBody, err := client.call(call, values, nil, -1) | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 	defer http.DrainBody(respBody) | 
					
						
							| 
									
										
										
										
											2019-08-29 07:12:57 +08:00
										 |  |  | 	switch err { | 
					
						
							|  |  |  | 	case nil: | 
					
						
							|  |  |  | 		return true, nil | 
					
						
							| 
									
										
										
										
											2019-11-26 08:39:43 +08:00
										 |  |  | 	case errLockConflict, errLockNotExpired: | 
					
						
							| 
									
										
										
										
											2019-08-29 07:12:57 +08:00
										 |  |  | 		return false, nil | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RLock calls read lock REST API.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) RLock(args dsync.LockArgs) (reply bool, err error) { | 
					
						
							|  |  |  | 	return client.restCall(lockRESTMethodRLock, args) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Lock calls lock REST API.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) Lock(args dsync.LockArgs) (reply bool, err error) { | 
					
						
							|  |  |  | 	return client.restCall(lockRESTMethodLock, args) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RUnlock calls read unlock REST API.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) RUnlock(args dsync.LockArgs) (reply bool, err error) { | 
					
						
							|  |  |  | 	return client.restCall(lockRESTMethodRUnlock, args) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Unlock calls write unlock RPC.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) Unlock(args dsync.LockArgs) (reply bool, err error) { | 
					
						
							|  |  |  | 	return client.restCall(lockRESTMethodUnlock, args) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-26 08:39:43 +08:00
										 |  |  | // Expired calls expired handler to check if lock args have expired.
 | 
					
						
							|  |  |  | func (client *lockRESTClient) Expired(args dsync.LockArgs) (expired bool, err error) { | 
					
						
							|  |  |  | 	return client.restCall(lockRESTMethodExpired, args) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | func closeLockers(lockers []dsync.NetLocker) { | 
					
						
							|  |  |  | 	for _, locker := range lockers { | 
					
						
							|  |  |  | 		locker.Close() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | func newLockAPI(endpoint Endpoint) dsync.NetLocker { | 
					
						
							|  |  |  | 	if endpoint.IsLocal { | 
					
						
							|  |  |  | 		return globalLockServers[endpoint] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return newlockRESTClient(endpoint) | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Returns a lock rest client.
 | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | func newlockRESTClient(endpoint Endpoint) *lockRESTClient { | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 	serverURL := &url.URL{ | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 		Scheme: endpoint.Scheme, | 
					
						
							|  |  |  | 		Host:   endpoint.Host, | 
					
						
							|  |  |  | 		Path:   pathJoin(lockRESTPrefix, endpoint.Path, lockRESTVersion), | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var tlsConfig *tls.Config | 
					
						
							|  |  |  | 	if globalIsSSL { | 
					
						
							|  |  |  | 		tlsConfig = &tls.Config{ | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 			ServerName: endpoint.Hostname(), | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 			RootCAs:    globalRootCAs, | 
					
						
							|  |  |  | 			NextProtos: []string{"http/1.1"}, // Force http1.1
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 	trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout, rest.DefaultRESTTimeout) | 
					
						
							|  |  |  | 	restClient, err := rest.NewClient(serverURL, trFn, newAuthToken) | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(context.Background(), err) | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 		return &lockRESTClient{endpoint: endpoint, restClient: restClient, connected: 0} | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-14 04:17:45 +08:00
										 |  |  | 	return &lockRESTClient{endpoint: endpoint, restClient: restClient, connected: 1} | 
					
						
							| 
									
										
										
										
											2019-04-18 14:16:27 +08:00
										 |  |  | } |