| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |  * Minio Cloud Storage, (C) 2016 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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-19 05:50:50 +08:00
										 |  |  | package cmd | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 	"bufio" | 
					
						
							|  |  |  | 	"crypto/tls" | 
					
						
							| 
									
										
										
										
											2016-10-19 02:46:33 +08:00
										 |  |  | 	"crypto/x509" | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 	"io" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | 	"net/rpc" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2016-10-18 05:31:33 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RPCClient is a wrapper type for rpc.Client which provides reconnect on first failure.
 | 
					
						
							|  |  |  | type RPCClient struct { | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 	mu         sync.Mutex | 
					
						
							|  |  |  | 	rpcPrivate *rpc.Client | 
					
						
							|  |  |  | 	node       string | 
					
						
							|  |  |  | 	rpcPath    string | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 	secureConn bool | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newClient constructs a RPCClient object with node and rpcPath initialized.
 | 
					
						
							|  |  |  | // It _doesn't_ connect to the remote endpoint. See Call method to see when the
 | 
					
						
							|  |  |  | // connect happens.
 | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | func newClient(node, rpcPath string, secureConn bool) *RPCClient { | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | 	return &RPCClient{ | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 		node:       node, | 
					
						
							|  |  |  | 		rpcPath:    rpcPath, | 
					
						
							|  |  |  | 		secureConn: secureConn, | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | // clearRPCClient clears the pointer to the rpc.Client object in a safe manner
 | 
					
						
							|  |  |  | func (rpcClient *RPCClient) clearRPCClient() { | 
					
						
							|  |  |  | 	rpcClient.mu.Lock() | 
					
						
							|  |  |  | 	rpcClient.rpcPrivate = nil | 
					
						
							|  |  |  | 	rpcClient.mu.Unlock() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // getRPCClient gets the pointer to the rpc.Client object in a safe manner
 | 
					
						
							|  |  |  | func (rpcClient *RPCClient) getRPCClient() *rpc.Client { | 
					
						
							|  |  |  | 	rpcClient.mu.Lock() | 
					
						
							|  |  |  | 	rpcLocalStack := rpcClient.rpcPrivate | 
					
						
							|  |  |  | 	rpcClient.mu.Unlock() | 
					
						
							|  |  |  | 	return rpcLocalStack | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // dialRPCClient tries to establish a connection to the server in a safe manner
 | 
					
						
							|  |  |  | func (rpcClient *RPCClient) dialRPCClient() (*rpc.Client, error) { | 
					
						
							|  |  |  | 	rpcClient.mu.Lock() | 
					
						
							|  |  |  | 	// After acquiring lock, check whether another thread may not have already dialed and established connection
 | 
					
						
							|  |  |  | 	if rpcClient.rpcPrivate != nil { | 
					
						
							| 
									
										
										
										
											2016-10-06 03:48:07 +08:00
										 |  |  | 		rpcClient.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 		return rpcClient.rpcPrivate, nil | 
					
						
							| 
									
										
										
										
											2016-08-16 05:33:48 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-10-06 03:48:07 +08:00
										 |  |  | 	rpcClient.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 	var conn net.Conn | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if rpcClient.secureConn { | 
					
						
							| 
									
										
										
										
											2016-10-20 22:43:31 +08:00
										 |  |  | 		hostname, _, splitErr := net.SplitHostPort(rpcClient.node) | 
					
						
							|  |  |  | 		if splitErr != nil { | 
					
						
							|  |  |  | 			return nil, errors.New("Unable to parse RPC address <" + rpcClient.node + "> : " + splitErr.Error()) | 
					
						
							| 
									
										
										
										
											2016-11-11 23:18:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-20 22:43:31 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		// ServerName in tls.Config needs to be specified to support SNI certificates
 | 
					
						
							| 
									
										
										
										
											2016-11-11 23:18:44 +08:00
										 |  |  | 		conn, err = tls.Dial("tcp", rpcClient.node, &tls.Config{ServerName: hostname, RootCAs: globalRootCAs}) | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2016-10-18 05:31:33 +08:00
										 |  |  | 		// Have a dial timeout with 3 secs.
 | 
					
						
							|  |  |  | 		conn, err = net.DialTimeout("tcp", rpcClient.node, 3*time.Second) | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-10-19 02:46:33 +08:00
										 |  |  | 		// Print RPC connection errors that are worthy to display in log
 | 
					
						
							|  |  |  | 		switch err.(type) { | 
					
						
							|  |  |  | 		case x509.HostnameError: | 
					
						
							|  |  |  | 			errorIf(err, "Unable to establish RPC to %s", rpcClient.node) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 	io.WriteString(conn, "CONNECT "+rpcClient.rpcPath+" HTTP/1.0\n\n") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-06 17:30:54 +08:00
										 |  |  | 	// Require successful HTTP response before switching to RPC protocol.
 | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 	resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"}) | 
					
						
							|  |  |  | 	if err == nil && resp.Status == "200 Connected to Go RPC" { | 
					
						
							|  |  |  | 		rpc := rpc.NewClient(conn) | 
					
						
							|  |  |  | 		if rpc == nil { | 
					
						
							|  |  |  | 			return nil, errors.New("No valid RPC Client created after dial") | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-10-06 03:48:07 +08:00
										 |  |  | 		rpcClient.mu.Lock() | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 		rpcClient.rpcPrivate = rpc | 
					
						
							| 
									
										
										
										
											2016-10-06 03:48:07 +08:00
										 |  |  | 		rpcClient.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2016-09-30 14:42:37 +08:00
										 |  |  | 		return rpc, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		err = errors.New("unexpected HTTP response: " + resp.Status) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	conn.Close() | 
					
						
							|  |  |  | 	return nil, &net.OpError{ | 
					
						
							|  |  |  | 		Op:   "dial-http", | 
					
						
							|  |  |  | 		Net:  rpcClient.node + " " + rpcClient.rpcPath, | 
					
						
							|  |  |  | 		Addr: nil, | 
					
						
							|  |  |  | 		Err:  err, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Call makes a RPC call to the remote endpoint using the default codec, namely encoding/gob.
 | 
					
						
							|  |  |  | func (rpcClient *RPCClient) Call(serviceMethod string, args interface{}, reply interface{}) error { | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 	// Make a copy below so that we can safely (continue to) work with the rpc.Client.
 | 
					
						
							|  |  |  | 	// Even in the case the two threads would simultaneously find that the connection is not initialised,
 | 
					
						
							|  |  |  | 	// they would both attempt to dial and only one of them would succeed in doing so.
 | 
					
						
							|  |  |  | 	rpcLocalStack := rpcClient.getRPCClient() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | 	// If the rpc.Client is nil, we attempt to (re)connect with the remote endpoint.
 | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 	if rpcLocalStack == nil { | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		rpcLocalStack, err = rpcClient.dialRPCClient() | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If the RPC fails due to a network-related error, then we reset
 | 
					
						
							|  |  |  | 	// rpc.Client for a subsequent reconnect.
 | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 	err := rpcLocalStack.Call(serviceMethod, args, reply) | 
					
						
							| 
									
										
										
										
											2016-08-29 11:04:47 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-11-10 23:44:41 +08:00
										 |  |  | 		// Any errors other than rpc.ErrShutdown just return quickly.
 | 
					
						
							|  |  |  | 		if err != rpc.ErrShutdown { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} // else rpc.ErrShutdown returned by rpc.Call
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Reset the underlying rpc connection before
 | 
					
						
							|  |  |  | 		// moving to reconnect.
 | 
					
						
							|  |  |  | 		rpcClient.clearRPCClient() | 
					
						
							| 
									
										
										
										
											2016-08-29 11:04:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-10 23:44:41 +08:00
										 |  |  | 		// Close the underlying connection before reconnect.
 | 
					
						
							|  |  |  | 		rpcLocalStack.Close() | 
					
						
							| 
									
										
										
										
											2016-08-29 11:04:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-10 23:44:41 +08:00
										 |  |  | 		// Try once more to re-connect.
 | 
					
						
							|  |  |  | 		rpcLocalStack, err = rpcClient.dialRPCClient() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2016-08-29 11:04:47 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-11-10 23:44:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Attempt the rpc.Call once again, upon any error now just give up.
 | 
					
						
							|  |  |  | 		err = rpcLocalStack.Call(serviceMethod, args, reply) | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Close closes the underlying socket file descriptor.
 | 
					
						
							|  |  |  | func (rpcClient *RPCClient) Close() error { | 
					
						
							|  |  |  | 	// See comment above for making a copy on local stack
 | 
					
						
							|  |  |  | 	rpcLocalStack := rpcClient.getRPCClient() | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-27 18:16:52 +08:00
										 |  |  | 	// If rpc client has not connected yet there is nothing to close.
 | 
					
						
							|  |  |  | 	if rpcLocalStack == nil { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reset rpcClient.rpc to allow for subsequent calls to use a new
 | 
					
						
							|  |  |  | 	// (socket) connection.
 | 
					
						
							|  |  |  | 	rpcClient.clearRPCClient() | 
					
						
							|  |  |  | 	return rpcLocalStack.Close() | 
					
						
							| 
									
										
										
										
											2016-08-15 15:00:18 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2016-09-16 15:30:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Node returns the node (network address) of the connection
 | 
					
						
							|  |  |  | func (rpcClient *RPCClient) Node() string { | 
					
						
							|  |  |  | 	return rpcClient.node | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RPCPath returns the RPC path of the connection
 | 
					
						
							|  |  |  | func (rpcClient *RPCClient) RPCPath() string { | 
					
						
							|  |  |  | 	return rpcClient.rpcPath | 
					
						
							|  |  |  | } |