github.com/bigcommerce/nomad@v0.9.3-bc/client/rpc.go (about)

     1  package client
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"net"
     7  	"net/rpc"
     8  	"strings"
     9  	"time"
    10  
    11  	metrics "github.com/armon/go-metrics"
    12  	"github.com/hashicorp/consul/lib"
    13  	"github.com/hashicorp/nomad/client/servers"
    14  	inmem "github.com/hashicorp/nomad/helper/codec"
    15  	"github.com/hashicorp/nomad/helper/pool"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	"github.com/hashicorp/yamux"
    18  	"github.com/ugorji/go/codec"
    19  )
    20  
    21  // rpcEndpoints holds the RPC endpoints
    22  type rpcEndpoints struct {
    23  	ClientStats *ClientStats
    24  	FileSystem  *FileSystem
    25  	Allocations *Allocations
    26  }
    27  
    28  // ClientRPC is used to make a local, client only RPC call
    29  func (c *Client) ClientRPC(method string, args interface{}, reply interface{}) error {
    30  	codec := &inmem.InmemCodec{
    31  		Method: method,
    32  		Args:   args,
    33  		Reply:  reply,
    34  	}
    35  	if err := c.rpcServer.ServeRequest(codec); err != nil {
    36  		return err
    37  	}
    38  	return codec.Err
    39  }
    40  
    41  // StreamingRpcHandler is used to make a local, client only streaming RPC
    42  // call.
    43  func (c *Client) StreamingRpcHandler(method string) (structs.StreamingRpcHandler, error) {
    44  	return c.streamingRpcs.GetHandler(method)
    45  }
    46  
    47  // RPC is used to forward an RPC call to a nomad server, or fail if no servers.
    48  func (c *Client) RPC(method string, args interface{}, reply interface{}) error {
    49  	// Invoke the RPCHandler if it exists
    50  	if c.config.RPCHandler != nil {
    51  		return c.config.RPCHandler.RPC(method, args, reply)
    52  	}
    53  
    54  	// This is subtle but we start measuring the time on the client side
    55  	// right at the time of the first request, vs. on the first retry as
    56  	// is done on the server side inside forward(). This is because the
    57  	// servers may already be applying the RPCHoldTimeout up there, so by
    58  	// starting the timer here we won't potentially double up the delay.
    59  	firstCheck := time.Now()
    60  
    61  TRY:
    62  	server := c.servers.FindServer()
    63  	if server == nil {
    64  		return noServersErr
    65  	}
    66  
    67  	// Make the request.
    68  	rpcErr := c.connPool.RPC(c.Region(), server.Addr, c.RPCMajorVersion(), method, args, reply)
    69  	if rpcErr == nil {
    70  		c.fireRpcRetryWatcher()
    71  		return nil
    72  	}
    73  
    74  	// If shutting down, exit without logging the error
    75  	select {
    76  	case <-c.shutdownCh:
    77  		return nil
    78  	default:
    79  	}
    80  
    81  	// Move off to another server, and see if we can retry.
    82  	c.rpcLogger.Error("error performing RPC to server", "error", rpcErr, "rpc", method, "server", server.Addr)
    83  	c.servers.NotifyFailedServer(server)
    84  	if retry := canRetry(args, rpcErr); !retry {
    85  		return rpcErr
    86  	}
    87  
    88  	// We can wait a bit and retry!
    89  	if time.Since(firstCheck) < c.config.RPCHoldTimeout {
    90  		jitter := lib.RandomStagger(c.config.RPCHoldTimeout / structs.JitterFraction)
    91  		select {
    92  		case <-time.After(jitter):
    93  			goto TRY
    94  		case <-c.shutdownCh:
    95  		}
    96  	}
    97  	return rpcErr
    98  }
    99  
   100  // canRetry returns true if the given situation is safe for a retry.
   101  func canRetry(args interface{}, err error) bool {
   102  	// No leader errors are always safe to retry since no state could have
   103  	// been changed.
   104  	if structs.IsErrNoLeader(err) {
   105  		return true
   106  	}
   107  
   108  	// Reads are safe to retry for stream errors, such as if a server was
   109  	// being shut down.
   110  	info, ok := args.(structs.RPCInfo)
   111  	if ok && info.IsRead() && lib.IsErrEOF(err) {
   112  		return true
   113  	}
   114  
   115  	return false
   116  }
   117  
   118  // RemoteStreamingRpcHandler is used to make a streaming RPC call to a remote
   119  // server.
   120  func (c *Client) RemoteStreamingRpcHandler(method string) (structs.StreamingRpcHandler, error) {
   121  	server := c.servers.FindServer()
   122  	if server == nil {
   123  		return nil, noServersErr
   124  	}
   125  
   126  	conn, err := c.streamingRpcConn(server, method)
   127  	if err != nil {
   128  		// Move off to another server
   129  		c.rpcLogger.Error("error performing RPC to server", "error", err, "rpc", method, "server", server.Addr)
   130  		c.servers.NotifyFailedServer(server)
   131  		return nil, err
   132  	}
   133  
   134  	return bridgedStreamingRpcHandler(conn), nil
   135  }
   136  
   137  // bridgedStreamingRpcHandler creates a bridged streaming RPC handler by copying
   138  // data between the two sides.
   139  func bridgedStreamingRpcHandler(sideA io.ReadWriteCloser) structs.StreamingRpcHandler {
   140  	return func(sideB io.ReadWriteCloser) {
   141  		defer sideA.Close()
   142  		defer sideB.Close()
   143  		structs.Bridge(sideA, sideB)
   144  	}
   145  }
   146  
   147  // streamingRpcConn is used to retrieve a connection to a server to conduct a
   148  // streaming RPC.
   149  func (c *Client) streamingRpcConn(server *servers.Server, method string) (net.Conn, error) {
   150  	// Dial the server
   151  	conn, err := net.DialTimeout("tcp", server.Addr.String(), 10*time.Second)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	// Cast to TCPConn
   157  	if tcp, ok := conn.(*net.TCPConn); ok {
   158  		tcp.SetKeepAlive(true)
   159  		tcp.SetNoDelay(true)
   160  	}
   161  
   162  	// Check if TLS is enabled
   163  	c.tlsWrapLock.RLock()
   164  	tlsWrap := c.tlsWrap
   165  	c.tlsWrapLock.RUnlock()
   166  
   167  	if tlsWrap != nil {
   168  		// Switch the connection into TLS mode
   169  		if _, err := conn.Write([]byte{byte(pool.RpcTLS)}); err != nil {
   170  			conn.Close()
   171  			return nil, err
   172  		}
   173  
   174  		// Wrap the connection in a TLS client
   175  		tlsConn, err := tlsWrap(c.Region(), conn)
   176  		if err != nil {
   177  			conn.Close()
   178  			return nil, err
   179  		}
   180  		conn = tlsConn
   181  	}
   182  
   183  	// Write the multiplex byte to set the mode
   184  	if _, err := conn.Write([]byte{byte(pool.RpcStreaming)}); err != nil {
   185  		conn.Close()
   186  		return nil, err
   187  	}
   188  
   189  	// Send the header
   190  	encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
   191  	decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
   192  	header := structs.StreamingRpcHeader{
   193  		Method: method,
   194  	}
   195  	if err := encoder.Encode(header); err != nil {
   196  		conn.Close()
   197  		return nil, err
   198  	}
   199  
   200  	// Wait for the acknowledgement
   201  	var ack structs.StreamingRpcAck
   202  	if err := decoder.Decode(&ack); err != nil {
   203  		conn.Close()
   204  		return nil, err
   205  	}
   206  
   207  	if ack.Error != "" {
   208  		conn.Close()
   209  		return nil, errors.New(ack.Error)
   210  	}
   211  
   212  	return conn, nil
   213  }
   214  
   215  // setupClientRpc is used to setup the Client's RPC endpoints
   216  func (c *Client) setupClientRpc() {
   217  	// Initialize the RPC handlers
   218  	c.endpoints.ClientStats = &ClientStats{c}
   219  	c.endpoints.FileSystem = NewFileSystemEndpoint(c)
   220  	c.endpoints.Allocations = NewAllocationsEndpoint(c)
   221  
   222  	// Create the RPC Server
   223  	c.rpcServer = rpc.NewServer()
   224  
   225  	// Register the endpoints with the RPC server
   226  	c.setupClientRpcServer(c.rpcServer)
   227  
   228  	go c.rpcConnListener()
   229  }
   230  
   231  // setupClientRpcServer is used to populate a client RPC server with endpoints.
   232  func (c *Client) setupClientRpcServer(server *rpc.Server) {
   233  	// Register the endpoints
   234  	server.Register(c.endpoints.ClientStats)
   235  	server.Register(c.endpoints.FileSystem)
   236  	server.Register(c.endpoints.Allocations)
   237  }
   238  
   239  // rpcConnListener is a long lived function that listens for new connections
   240  // being made on the connection pool and starts an RPC listener for each
   241  // connection.
   242  func (c *Client) rpcConnListener() {
   243  	// Make a channel for new connections.
   244  	conns := make(chan *yamux.Session, 4)
   245  	c.connPool.SetConnListener(conns)
   246  
   247  	for {
   248  		select {
   249  		case <-c.shutdownCh:
   250  			return
   251  		case session, ok := <-conns:
   252  			if !ok {
   253  				continue
   254  			}
   255  
   256  			go c.listenConn(session)
   257  		}
   258  	}
   259  }
   260  
   261  // listenConn is used to listen for connections being made from the server on
   262  // pre-existing connection. This should be called in a goroutine.
   263  func (c *Client) listenConn(s *yamux.Session) {
   264  	for {
   265  		conn, err := s.Accept()
   266  		if err != nil {
   267  			if s.IsClosed() {
   268  				return
   269  			}
   270  
   271  			c.rpcLogger.Error("failed to accept RPC conn", "error", err)
   272  			continue
   273  		}
   274  
   275  		go c.handleConn(conn)
   276  		metrics.IncrCounter([]string{"client", "rpc", "accept_conn"}, 1)
   277  	}
   278  }
   279  
   280  // handleConn is used to determine if this is a RPC or Streaming RPC connection and
   281  // invoke the correct handler
   282  func (c *Client) handleConn(conn net.Conn) {
   283  	// Read a single byte
   284  	buf := make([]byte, 1)
   285  	if _, err := conn.Read(buf); err != nil {
   286  		if err != io.EOF {
   287  			c.rpcLogger.Error("error reading byte", "error", err)
   288  		}
   289  		conn.Close()
   290  		return
   291  	}
   292  
   293  	// Switch on the byte
   294  	switch pool.RPCType(buf[0]) {
   295  	case pool.RpcNomad:
   296  		c.handleNomadConn(conn)
   297  
   298  	case pool.RpcStreaming:
   299  		c.handleStreamingConn(conn)
   300  
   301  	default:
   302  		c.rpcLogger.Error("unrecognized RPC byte", "byte", buf[0])
   303  		conn.Close()
   304  		return
   305  	}
   306  }
   307  
   308  // handleNomadConn is used to handle a single Nomad RPC connection.
   309  func (c *Client) handleNomadConn(conn net.Conn) {
   310  	defer conn.Close()
   311  	rpcCodec := pool.NewServerCodec(conn)
   312  	for {
   313  		select {
   314  		case <-c.shutdownCh:
   315  			return
   316  		default:
   317  		}
   318  
   319  		if err := c.rpcServer.ServeRequest(rpcCodec); err != nil {
   320  			if err != io.EOF && !strings.Contains(err.Error(), "closed") {
   321  				c.rpcLogger.Error("error performing RPC", "error", err, "addr", conn.RemoteAddr())
   322  				metrics.IncrCounter([]string{"client", "rpc", "request_error"}, 1)
   323  			}
   324  			return
   325  		}
   326  		metrics.IncrCounter([]string{"client", "rpc", "request"}, 1)
   327  	}
   328  }
   329  
   330  // handleStreamingConn is used to handle a single Streaming Nomad RPC connection.
   331  func (c *Client) handleStreamingConn(conn net.Conn) {
   332  	defer conn.Close()
   333  
   334  	// Decode the header
   335  	var header structs.StreamingRpcHeader
   336  	decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
   337  	if err := decoder.Decode(&header); err != nil {
   338  		if err != io.EOF && !strings.Contains(err.Error(), "closed") {
   339  			c.rpcLogger.Error("error performing streaming RPC", "error", err, "addr", conn.RemoteAddr())
   340  			metrics.IncrCounter([]string{"client", "streaming_rpc", "request_error"}, 1)
   341  		}
   342  
   343  		return
   344  	}
   345  
   346  	ack := structs.StreamingRpcAck{}
   347  	handler, err := c.streamingRpcs.GetHandler(header.Method)
   348  	if err != nil {
   349  		c.rpcLogger.Error("streaming RPC error", "addr", conn.RemoteAddr(), "error", err)
   350  		metrics.IncrCounter([]string{"client", "streaming_rpc", "request_error"}, 1)
   351  		ack.Error = err.Error()
   352  	}
   353  
   354  	// Send the acknowledgement
   355  	encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
   356  	if err := encoder.Encode(ack); err != nil {
   357  		conn.Close()
   358  		return
   359  	}
   360  
   361  	if ack.Error != "" {
   362  		return
   363  	}
   364  
   365  	// Invoke the handler
   366  	metrics.IncrCounter([]string{"client", "streaming_rpc", "request"}, 1)
   367  	handler(conn)
   368  }
   369  
   370  // resolveServer given a sever's address as a string, return it's resolved
   371  // net.Addr or an error.
   372  func resolveServer(s string) (net.Addr, error) {
   373  	const defaultClientPort = "4647" // default client RPC port
   374  	host, port, err := net.SplitHostPort(s)
   375  	if err != nil {
   376  		if strings.Contains(err.Error(), "missing port") {
   377  			host = s
   378  			port = defaultClientPort
   379  		} else {
   380  			return nil, err
   381  		}
   382  	}
   383  	return net.ResolveTCPAddr("tcp", net.JoinHostPort(host, port))
   384  }
   385  
   386  // Ping is used to ping a particular server and returns whether it is healthy or
   387  // a potential error.
   388  func (c *Client) Ping(srv net.Addr) error {
   389  	var reply struct{}
   390  	err := c.connPool.RPC(c.Region(), srv, c.RPCMajorVersion(), "Status.Ping", struct{}{}, &reply)
   391  	return err
   392  }
   393  
   394  // rpcRetryWatcher returns a channel that will be closed if an event happens
   395  // such that we expect the next RPC to be successful.
   396  func (c *Client) rpcRetryWatcher() <-chan struct{} {
   397  	c.rpcRetryLock.Lock()
   398  	defer c.rpcRetryLock.Unlock()
   399  
   400  	if c.rpcRetryCh == nil {
   401  		c.rpcRetryCh = make(chan struct{})
   402  	}
   403  
   404  	return c.rpcRetryCh
   405  }
   406  
   407  // fireRpcRetryWatcher causes any RPC retryloops to retry their RPCs because we
   408  // believe the will be successful.
   409  func (c *Client) fireRpcRetryWatcher() {
   410  	c.rpcRetryLock.Lock()
   411  	defer c.rpcRetryLock.Unlock()
   412  	if c.rpcRetryCh != nil {
   413  		close(c.rpcRetryCh)
   414  		c.rpcRetryCh = nil
   415  	}
   416  }