github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/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  		return nil
    71  	}
    72  
    73  	// Move off to another server, and see if we can retry.
    74  	c.logger.Printf("[ERR] nomad: %q RPC failed to server %s: %v", method, server.Addr, rpcErr)
    75  	c.servers.NotifyFailedServer(server)
    76  	if retry := canRetry(args, rpcErr); !retry {
    77  		return rpcErr
    78  	}
    79  
    80  	// We can wait a bit and retry!
    81  	if time.Since(firstCheck) < c.config.RPCHoldTimeout {
    82  		jitter := lib.RandomStagger(c.config.RPCHoldTimeout / structs.JitterFraction)
    83  		select {
    84  		case <-time.After(jitter):
    85  			goto TRY
    86  		case <-c.shutdownCh:
    87  		}
    88  	}
    89  	return rpcErr
    90  }
    91  
    92  // canRetry returns true if the given situation is safe for a retry.
    93  func canRetry(args interface{}, err error) bool {
    94  	// No leader errors are always safe to retry since no state could have
    95  	// been changed.
    96  	if structs.IsErrNoLeader(err) {
    97  		return true
    98  	}
    99  
   100  	// Reads are safe to retry for stream errors, such as if a server was
   101  	// being shut down.
   102  	info, ok := args.(structs.RPCInfo)
   103  	if ok && info.IsRead() && lib.IsErrEOF(err) {
   104  		return true
   105  	}
   106  
   107  	return false
   108  }
   109  
   110  // RemoteStreamingRpcHandler is used to make a streaming RPC call to a remote
   111  // server.
   112  func (c *Client) RemoteStreamingRpcHandler(method string) (structs.StreamingRpcHandler, error) {
   113  	server := c.servers.FindServer()
   114  	if server == nil {
   115  		return nil, noServersErr
   116  	}
   117  
   118  	conn, err := c.streamingRpcConn(server, method)
   119  	if err != nil {
   120  		// Move off to another server
   121  		c.logger.Printf("[ERR] nomad: %q RPC failed to server %s: %v", method, server.Addr, err)
   122  		c.servers.NotifyFailedServer(server)
   123  		return nil, err
   124  	}
   125  
   126  	return bridgedStreamingRpcHandler(conn), nil
   127  }
   128  
   129  // bridgedStreamingRpcHandler creates a bridged streaming RPC handler by copying
   130  // data between the two sides.
   131  func bridgedStreamingRpcHandler(sideA io.ReadWriteCloser) structs.StreamingRpcHandler {
   132  	return func(sideB io.ReadWriteCloser) {
   133  		defer sideA.Close()
   134  		defer sideB.Close()
   135  		structs.Bridge(sideA, sideB)
   136  	}
   137  }
   138  
   139  // streamingRpcConn is used to retrieve a connection to a server to conduct a
   140  // streaming RPC.
   141  func (c *Client) streamingRpcConn(server *servers.Server, method string) (net.Conn, error) {
   142  	// Dial the server
   143  	conn, err := net.DialTimeout("tcp", server.Addr.String(), 10*time.Second)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	// Cast to TCPConn
   149  	if tcp, ok := conn.(*net.TCPConn); ok {
   150  		tcp.SetKeepAlive(true)
   151  		tcp.SetNoDelay(true)
   152  	}
   153  
   154  	// Check if TLS is enabled
   155  	c.tlsWrapLock.RLock()
   156  	tlsWrap := c.tlsWrap
   157  	c.tlsWrapLock.RUnlock()
   158  
   159  	if tlsWrap != nil {
   160  		// Switch the connection into TLS mode
   161  		if _, err := conn.Write([]byte{byte(pool.RpcTLS)}); err != nil {
   162  			conn.Close()
   163  			return nil, err
   164  		}
   165  
   166  		// Wrap the connection in a TLS client
   167  		tlsConn, err := tlsWrap(c.Region(), conn)
   168  		if err != nil {
   169  			conn.Close()
   170  			return nil, err
   171  		}
   172  		conn = tlsConn
   173  	}
   174  
   175  	// Write the multiplex byte to set the mode
   176  	if _, err := conn.Write([]byte{byte(pool.RpcStreaming)}); err != nil {
   177  		conn.Close()
   178  		return nil, err
   179  	}
   180  
   181  	// Send the header
   182  	encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
   183  	decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
   184  	header := structs.StreamingRpcHeader{
   185  		Method: method,
   186  	}
   187  	if err := encoder.Encode(header); err != nil {
   188  		conn.Close()
   189  		return nil, err
   190  	}
   191  
   192  	// Wait for the acknowledgement
   193  	var ack structs.StreamingRpcAck
   194  	if err := decoder.Decode(&ack); err != nil {
   195  		conn.Close()
   196  		return nil, err
   197  	}
   198  
   199  	if ack.Error != "" {
   200  		conn.Close()
   201  		return nil, errors.New(ack.Error)
   202  	}
   203  
   204  	return conn, nil
   205  }
   206  
   207  // setupClientRpc is used to setup the Client's RPC endpoints
   208  func (c *Client) setupClientRpc() {
   209  	// Initialize the RPC handlers
   210  	c.endpoints.ClientStats = &ClientStats{c}
   211  	c.endpoints.FileSystem = NewFileSystemEndpoint(c)
   212  	c.endpoints.Allocations = &Allocations{c}
   213  
   214  	// Create the RPC Server
   215  	c.rpcServer = rpc.NewServer()
   216  
   217  	// Register the endpoints with the RPC server
   218  	c.setupClientRpcServer(c.rpcServer)
   219  
   220  	go c.rpcConnListener()
   221  }
   222  
   223  // setupClientRpcServer is used to populate a client RPC server with endpoints.
   224  func (c *Client) setupClientRpcServer(server *rpc.Server) {
   225  	// Register the endpoints
   226  	server.Register(c.endpoints.ClientStats)
   227  	server.Register(c.endpoints.FileSystem)
   228  	server.Register(c.endpoints.Allocations)
   229  }
   230  
   231  // rpcConnListener is a long lived function that listens for new connections
   232  // being made on the connection pool and starts an RPC listener for each
   233  // connection.
   234  func (c *Client) rpcConnListener() {
   235  	// Make a channel for new connections.
   236  	conns := make(chan *yamux.Session, 4)
   237  	c.connPool.SetConnListener(conns)
   238  
   239  	for {
   240  		select {
   241  		case <-c.shutdownCh:
   242  			return
   243  		case session, ok := <-conns:
   244  			if !ok {
   245  				continue
   246  			}
   247  
   248  			go c.listenConn(session)
   249  		}
   250  	}
   251  }
   252  
   253  // listenConn is used to listen for connections being made from the server on
   254  // pre-existing connection. This should be called in a goroutine.
   255  func (c *Client) listenConn(s *yamux.Session) {
   256  	for {
   257  		conn, err := s.Accept()
   258  		if err != nil {
   259  			if s.IsClosed() {
   260  				return
   261  			}
   262  
   263  			c.logger.Printf("[ERR] client.rpc: failed to accept RPC conn: %v", err)
   264  			continue
   265  		}
   266  
   267  		go c.handleConn(conn)
   268  		metrics.IncrCounter([]string{"client", "rpc", "accept_conn"}, 1)
   269  	}
   270  }
   271  
   272  // handleConn is used to determine if this is a RPC or Streaming RPC connection and
   273  // invoke the correct handler
   274  func (c *Client) handleConn(conn net.Conn) {
   275  	// Read a single byte
   276  	buf := make([]byte, 1)
   277  	if _, err := conn.Read(buf); err != nil {
   278  		if err != io.EOF {
   279  			c.logger.Printf("[ERR] client.rpc: failed to read byte: %v", err)
   280  		}
   281  		conn.Close()
   282  		return
   283  	}
   284  
   285  	// Switch on the byte
   286  	switch pool.RPCType(buf[0]) {
   287  	case pool.RpcNomad:
   288  		c.handleNomadConn(conn)
   289  
   290  	case pool.RpcStreaming:
   291  		c.handleStreamingConn(conn)
   292  
   293  	default:
   294  		c.logger.Printf("[ERR] client.rpc: unrecognized RPC byte: %v", buf[0])
   295  		conn.Close()
   296  		return
   297  	}
   298  }
   299  
   300  // handleNomadConn is used to handle a single Nomad RPC connection.
   301  func (c *Client) handleNomadConn(conn net.Conn) {
   302  	defer conn.Close()
   303  	rpcCodec := pool.NewServerCodec(conn)
   304  	for {
   305  		select {
   306  		case <-c.shutdownCh:
   307  			return
   308  		default:
   309  		}
   310  
   311  		if err := c.rpcServer.ServeRequest(rpcCodec); err != nil {
   312  			if err != io.EOF && !strings.Contains(err.Error(), "closed") {
   313  				c.logger.Printf("[ERR] client.rpc: RPC error: %v (%v)", err, conn)
   314  				metrics.IncrCounter([]string{"client", "rpc", "request_error"}, 1)
   315  			}
   316  			return
   317  		}
   318  		metrics.IncrCounter([]string{"client", "rpc", "request"}, 1)
   319  	}
   320  }
   321  
   322  // handleStreamingConn is used to handle a single Streaming Nomad RPC connection.
   323  func (c *Client) handleStreamingConn(conn net.Conn) {
   324  	defer conn.Close()
   325  
   326  	// Decode the header
   327  	var header structs.StreamingRpcHeader
   328  	decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
   329  	if err := decoder.Decode(&header); err != nil {
   330  		if err != io.EOF && !strings.Contains(err.Error(), "closed") {
   331  			c.logger.Printf("[ERR] client.rpc: Streaming RPC error: %v (%v)", err, conn)
   332  			metrics.IncrCounter([]string{"client", "streaming_rpc", "request_error"}, 1)
   333  		}
   334  
   335  		return
   336  	}
   337  
   338  	ack := structs.StreamingRpcAck{}
   339  	handler, err := c.streamingRpcs.GetHandler(header.Method)
   340  	if err != nil {
   341  		c.logger.Printf("[ERR] client.rpc: Streaming RPC error: %v (%v)", err, conn)
   342  		metrics.IncrCounter([]string{"client", "streaming_rpc", "request_error"}, 1)
   343  		ack.Error = err.Error()
   344  	}
   345  
   346  	// Send the acknowledgement
   347  	encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
   348  	if err := encoder.Encode(ack); err != nil {
   349  		conn.Close()
   350  		return
   351  	}
   352  
   353  	if ack.Error != "" {
   354  		return
   355  	}
   356  
   357  	// Invoke the handler
   358  	metrics.IncrCounter([]string{"client", "streaming_rpc", "request"}, 1)
   359  	handler(conn)
   360  }
   361  
   362  // resolveServer given a sever's address as a string, return it's resolved
   363  // net.Addr or an error.
   364  func resolveServer(s string) (net.Addr, error) {
   365  	const defaultClientPort = "4647" // default client RPC port
   366  	host, port, err := net.SplitHostPort(s)
   367  	if err != nil {
   368  		if strings.Contains(err.Error(), "missing port") {
   369  			host = s
   370  			port = defaultClientPort
   371  		} else {
   372  			return nil, err
   373  		}
   374  	}
   375  	return net.ResolveTCPAddr("tcp", net.JoinHostPort(host, port))
   376  }
   377  
   378  // Ping is used to ping a particular server and returns whether it is healthy or
   379  // a potential error.
   380  func (c *Client) Ping(srv net.Addr) error {
   381  	var reply struct{}
   382  	err := c.connPool.RPC(c.Region(), srv, c.RPCMajorVersion(), "Status.Ping", struct{}{}, &reply)
   383  	return err
   384  }