| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |  * 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 ( | 
					
						
							|  |  |  | 	"crypto/tls" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	xrpc "github.com/minio/minio/cmd/rpc" | 
					
						
							|  |  |  | 	xnet "github.com/minio/minio/pkg/net" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DefaultSkewTime - skew time is 15 minutes between minio peers.
 | 
					
						
							|  |  |  | const DefaultSkewTime = 15 * time.Minute | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-13 15:27:33 +08:00
										 |  |  | // defaultRPCTimeout - default RPC timeout is one minute.
 | 
					
						
							|  |  |  | const defaultRPCTimeout = 5 * time.Minute | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // defaultRPCRetryTime - default RPC time to wait before retry after a network error
 | 
					
						
							|  |  |  | const defaultRPCRetryTime = 1 * time.Minute | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | var errRPCRetry = fmt.Errorf("rpc: retry error") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func isNetError(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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RPCVersion - RPC semantic version based on semver 2.0.0 https://semver.org/.
 | 
					
						
							|  |  |  | type RPCVersion struct { | 
					
						
							|  |  |  | 	Major uint64 | 
					
						
							|  |  |  | 	Minor uint64 | 
					
						
							|  |  |  | 	Patch uint64 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Compare - compares given version with this version.
 | 
					
						
							|  |  |  | func (v RPCVersion) Compare(o RPCVersion) int { | 
					
						
							|  |  |  | 	compare := func(v1, v2 uint64) int { | 
					
						
							|  |  |  | 		if v1 == v2 { | 
					
						
							|  |  |  | 			return 0 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if v1 > v2 { | 
					
						
							|  |  |  | 			return 1 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return -1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if r := compare(v.Major, o.Major); r != 0 { | 
					
						
							|  |  |  | 		return r | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if r := compare(v.Minor, o.Minor); r != 0 { | 
					
						
							|  |  |  | 		return r | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return compare(v.Patch, o.Patch) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (v RPCVersion) String() string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("%v.%v.%v", v.Major, v.Minor, v.Patch) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AuthArgs - base argument for any RPC call for authentication.
 | 
					
						
							|  |  |  | type AuthArgs struct { | 
					
						
							|  |  |  | 	Token       string | 
					
						
							|  |  |  | 	RPCVersion  RPCVersion | 
					
						
							|  |  |  | 	RequestTime time.Time | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Authenticate - checks if given arguments are valid to allow RPC call.
 | 
					
						
							|  |  |  | // This is xrpc.Authenticator and is called in RPC server.
 | 
					
						
							|  |  |  | func (args AuthArgs) Authenticate() error { | 
					
						
							|  |  |  | 	// Check whether request time is within acceptable skew time.
 | 
					
						
							|  |  |  | 	utcNow := time.Now().UTC() | 
					
						
							|  |  |  | 	if args.RequestTime.Sub(utcNow) > DefaultSkewTime || utcNow.Sub(args.RequestTime) > DefaultSkewTime { | 
					
						
							|  |  |  | 		return fmt.Errorf("client time %v is too apart with server time %v", args.RequestTime, utcNow) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if globalRPCAPIVersion.Compare(args.RPCVersion) != 0 { | 
					
						
							|  |  |  | 		return fmt.Errorf("version mismatch. expected: %v, received: %v", globalRPCAPIVersion, args.RPCVersion) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !isAuthTokenValid(args.Token) { | 
					
						
							|  |  |  | 		return errAuthentication | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SetAuthArgs - sets given authentication arguments to this args. This is called in RPC client.
 | 
					
						
							|  |  |  | func (args *AuthArgs) SetAuthArgs(authArgs AuthArgs) { | 
					
						
							|  |  |  | 	*args = authArgs | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // VoidReply - void (empty) RPC reply.
 | 
					
						
							|  |  |  | type VoidReply struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RPCClientArgs - RPC client arguments.
 | 
					
						
							|  |  |  | type RPCClientArgs struct { | 
					
						
							|  |  |  | 	NewAuthTokenFunc func() string | 
					
						
							|  |  |  | 	RPCVersion       RPCVersion | 
					
						
							|  |  |  | 	ServiceName      string | 
					
						
							|  |  |  | 	ServiceURL       *xnet.URL | 
					
						
							|  |  |  | 	TLSConfig        *tls.Config | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // validate - checks whether given args are valid or not.
 | 
					
						
							|  |  |  | func (args RPCClientArgs) validate() error { | 
					
						
							|  |  |  | 	if args.NewAuthTokenFunc == nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("NewAuthTokenFunc must not be empty") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if args.ServiceName == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("ServiceName must not be empty") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if args.ServiceURL.Scheme != "http" && args.ServiceURL.Scheme != "https" { | 
					
						
							|  |  |  | 		return fmt.Errorf("unknown RPC URL %v", args.ServiceURL) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if args.ServiceURL.User != nil || args.ServiceURL.ForceQuery || args.ServiceURL.RawQuery != "" || args.ServiceURL.Fragment != "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("unknown RPC URL %v", args.ServiceURL) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if args.ServiceURL.Scheme == "https" && args.TLSConfig == nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("tls configuration must not be empty for https url %v", args.ServiceURL) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RPCClient - base RPC client.
 | 
					
						
							|  |  |  | type RPCClient struct { | 
					
						
							|  |  |  | 	sync.RWMutex | 
					
						
							|  |  |  | 	args        RPCClientArgs | 
					
						
							|  |  |  | 	authToken   string | 
					
						
							|  |  |  | 	rpcClient   *xrpc.Client | 
					
						
							|  |  |  | 	retryTicker *time.Ticker | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (client *RPCClient) setRetryTicker(ticker *time.Ticker) { | 
					
						
							| 
									
										
										
										
											2018-08-04 09:57:00 +08:00
										 |  |  | 	if ticker == nil { | 
					
						
							|  |  |  | 		client.RLock() | 
					
						
							|  |  |  | 		isNil := client.retryTicker == nil | 
					
						
							|  |  |  | 		client.RUnlock() | 
					
						
							|  |  |  | 		if isNil { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	client.Lock() | 
					
						
							|  |  |  | 	defer client.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if client.retryTicker != nil { | 
					
						
							|  |  |  | 		client.retryTicker.Stop() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client.retryTicker = ticker | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Call - calls servicemethod on remote server.
 | 
					
						
							|  |  |  | func (client *RPCClient) Call(serviceMethod string, args interface { | 
					
						
							|  |  |  | 	SetAuthArgs(args AuthArgs) | 
					
						
							|  |  |  | }, reply interface{}) (err error) { | 
					
						
							|  |  |  | 	lockedCall := func() error { | 
					
						
							|  |  |  | 		client.RLock() | 
					
						
							| 
									
										
										
										
											2018-08-04 09:57:00 +08:00
										 |  |  | 		retryTicker := client.retryTicker | 
					
						
							|  |  |  | 		client.RUnlock() | 
					
						
							|  |  |  | 		if retryTicker != nil { | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 			select { | 
					
						
							| 
									
										
										
										
											2018-08-04 09:57:00 +08:00
										 |  |  | 			case <-retryTicker.C: | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 			default: | 
					
						
							|  |  |  | 				return errRPCRetry | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-04 09:57:00 +08:00
										 |  |  | 		client.RLock() | 
					
						
							|  |  |  | 		authToken := client.authToken | 
					
						
							|  |  |  | 		client.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		// Make RPC call.
 | 
					
						
							| 
									
										
										
										
											2018-08-04 09:57:00 +08:00
										 |  |  | 		args.SetAuthArgs(AuthArgs{authToken, client.args.RPCVersion, time.Now().UTC()}) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		return client.rpcClient.Call(serviceMethod, args, reply) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	call := func() error { | 
					
						
							|  |  |  | 		err = lockedCall() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err == errRPCRetry { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if isNetError(err) { | 
					
						
							| 
									
										
										
										
											2019-02-13 15:27:33 +08:00
										 |  |  | 			client.setRetryTicker(time.NewTicker(defaultRPCRetryTime)) | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			client.setRetryTicker(nil) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If authentication error is received, retry the same call only once
 | 
					
						
							|  |  |  | 	// with new authentication token.
 | 
					
						
							|  |  |  | 	if err = call(); err == nil { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err.Error() != errAuthentication.Error() { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client.Lock() | 
					
						
							|  |  |  | 	client.authToken = client.args.NewAuthTokenFunc() | 
					
						
							|  |  |  | 	client.Unlock() | 
					
						
							|  |  |  | 	return call() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Close - closes underneath RPC client.
 | 
					
						
							|  |  |  | func (client *RPCClient) Close() error { | 
					
						
							|  |  |  | 	client.Lock() | 
					
						
							|  |  |  | 	defer client.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	client.authToken = "" | 
					
						
							|  |  |  | 	return client.rpcClient.Close() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServiceURL - returns service URL used for RPC call.
 | 
					
						
							|  |  |  | func (client *RPCClient) ServiceURL() *xnet.URL { | 
					
						
							|  |  |  | 	// Take copy of ServiceURL
 | 
					
						
							|  |  |  | 	u := *(client.args.ServiceURL) | 
					
						
							|  |  |  | 	return &u | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewRPCClient - returns new RPC client.
 | 
					
						
							|  |  |  | func NewRPCClient(args RPCClientArgs) (*RPCClient, error) { | 
					
						
							|  |  |  | 	if err := args.validate(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-15 09:53:46 +08:00
										 |  |  | 	rpcClient, err := xrpc.NewClient(args.ServiceURL, args.TLSConfig, defaultRPCTimeout) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	return &RPCClient{ | 
					
						
							|  |  |  | 		args:      args, | 
					
						
							|  |  |  | 		authToken: args.NewAuthTokenFunc(), | 
					
						
							| 
									
										
										
										
											2019-02-15 09:53:46 +08:00
										 |  |  | 		rpcClient: rpcClient, | 
					
						
							| 
									
										
										
										
											2018-06-06 16:51:56 +08:00
										 |  |  | 	}, nil | 
					
						
							|  |  |  | } |