github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/nomad/rpc.go (about)

     1  package nomad
     2  
     3  import (
     4  	"crypto/tls"
     5  	"fmt"
     6  	"io"
     7  	"math/rand"
     8  	"net"
     9  	"net/rpc"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/armon/go-metrics"
    14  	"github.com/hashicorp/consul/lib"
    15  	memdb "github.com/hashicorp/go-memdb"
    16  	"github.com/hashicorp/net-rpc-msgpackrpc"
    17  	"github.com/hashicorp/nomad/nomad/state"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  	"github.com/hashicorp/raft"
    20  	"github.com/hashicorp/yamux"
    21  )
    22  
    23  type RPCType byte
    24  
    25  const (
    26  	rpcNomad     RPCType = 0x01
    27  	rpcRaft              = 0x02
    28  	rpcMultiplex         = 0x03
    29  	rpcTLS               = 0x04
    30  )
    31  
    32  const (
    33  	// maxQueryTime is used to bound the limit of a blocking query
    34  	maxQueryTime = 300 * time.Second
    35  
    36  	// defaultQueryTime is the amount of time we block waiting for a change
    37  	// if no time is specified. Previously we would wait the maxQueryTime.
    38  	defaultQueryTime = 300 * time.Second
    39  
    40  	// jitterFraction is a the limit to the amount of jitter we apply
    41  	// to a user specified MaxQueryTime. We divide the specified time by
    42  	// the fraction. So 16 == 6.25% limit of jitter. This jitter is also
    43  	// applied to RPCHoldTimeout.
    44  	jitterFraction = 16
    45  
    46  	// Warn if the Raft command is larger than this.
    47  	// If it's over 1MB something is probably being abusive.
    48  	raftWarnSize = 1024 * 1024
    49  
    50  	// enqueueLimit caps how long we will wait to enqueue
    51  	// a new Raft command. Something is probably wrong if this
    52  	// value is ever reached. However, it prevents us from blocking
    53  	// the requesting goroutine forever.
    54  	enqueueLimit = 30 * time.Second
    55  )
    56  
    57  // NewClientCodec returns a new rpc.ClientCodec to be used to make RPC calls to
    58  // the Nomad Server.
    59  func NewClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec {
    60  	return msgpackrpc.NewCodecFromHandle(true, true, conn, structs.HashiMsgpackHandle)
    61  }
    62  
    63  // NewServerCodec returns a new rpc.ServerCodec to be used by the Nomad Server
    64  // to handle rpcs.
    65  func NewServerCodec(conn io.ReadWriteCloser) rpc.ServerCodec {
    66  	return msgpackrpc.NewCodecFromHandle(true, true, conn, structs.HashiMsgpackHandle)
    67  }
    68  
    69  // listen is used to listen for incoming RPC connections
    70  func (s *Server) listen() {
    71  	for {
    72  		// Accept a connection
    73  		conn, err := s.rpcListener.Accept()
    74  		if err != nil {
    75  			if s.shutdown {
    76  				return
    77  			}
    78  			s.logger.Printf("[ERR] nomad.rpc: failed to accept RPC conn: %v", err)
    79  			continue
    80  		}
    81  
    82  		go s.handleConn(conn, false)
    83  		metrics.IncrCounter([]string{"nomad", "rpc", "accept_conn"}, 1)
    84  	}
    85  }
    86  
    87  // handleConn is used to determine if this is a Raft or
    88  // Nomad type RPC connection and invoke the correct handler
    89  func (s *Server) handleConn(conn net.Conn, isTLS bool) {
    90  	// Read a single byte
    91  	buf := make([]byte, 1)
    92  	if _, err := conn.Read(buf); err != nil {
    93  		if err != io.EOF {
    94  			s.logger.Printf("[ERR] nomad.rpc: failed to read byte: %v", err)
    95  		}
    96  		conn.Close()
    97  		return
    98  	}
    99  
   100  	// Enforce TLS if EnableRPC is set
   101  	if s.config.TLSConfig.EnableRPC && !isTLS && RPCType(buf[0]) != rpcTLS {
   102  		s.logger.Printf("[WARN] nomad.rpc: Non-TLS connection attempted with RequireTLS set")
   103  		conn.Close()
   104  		return
   105  	}
   106  
   107  	// Switch on the byte
   108  	switch RPCType(buf[0]) {
   109  	case rpcNomad:
   110  		s.handleNomadConn(conn)
   111  
   112  	case rpcRaft:
   113  		metrics.IncrCounter([]string{"nomad", "rpc", "raft_handoff"}, 1)
   114  		s.raftLayer.Handoff(conn)
   115  
   116  	case rpcMultiplex:
   117  		s.handleMultiplex(conn)
   118  
   119  	case rpcTLS:
   120  		if s.rpcTLS == nil {
   121  			s.logger.Printf("[WARN] nomad.rpc: TLS connection attempted, server not configured for TLS")
   122  			conn.Close()
   123  			return
   124  		}
   125  		conn = tls.Server(conn, s.rpcTLS)
   126  		s.handleConn(conn, true)
   127  
   128  	default:
   129  		s.logger.Printf("[ERR] nomad.rpc: unrecognized RPC byte: %v", buf[0])
   130  		conn.Close()
   131  		return
   132  	}
   133  }
   134  
   135  // handleMultiplex is used to multiplex a single incoming connection
   136  // using the Yamux multiplexer
   137  func (s *Server) handleMultiplex(conn net.Conn) {
   138  	defer conn.Close()
   139  	conf := yamux.DefaultConfig()
   140  	conf.LogOutput = s.config.LogOutput
   141  	server, _ := yamux.Server(conn, conf)
   142  	for {
   143  		sub, err := server.Accept()
   144  		if err != nil {
   145  			if err != io.EOF {
   146  				s.logger.Printf("[ERR] nomad.rpc: multiplex conn accept failed: %v", err)
   147  			}
   148  			return
   149  		}
   150  		go s.handleNomadConn(sub)
   151  	}
   152  }
   153  
   154  // handleNomadConn is used to service a single Nomad RPC connection
   155  func (s *Server) handleNomadConn(conn net.Conn) {
   156  	defer conn.Close()
   157  	rpcCodec := NewServerCodec(conn)
   158  	for {
   159  		select {
   160  		case <-s.shutdownCh:
   161  			return
   162  		default:
   163  		}
   164  
   165  		if err := s.rpcServer.ServeRequest(rpcCodec); err != nil {
   166  			if err != io.EOF && !strings.Contains(err.Error(), "closed") {
   167  				s.logger.Printf("[ERR] nomad.rpc: RPC error: %v (%v)", err, conn)
   168  				metrics.IncrCounter([]string{"nomad", "rpc", "request_error"}, 1)
   169  			}
   170  			return
   171  		}
   172  		metrics.IncrCounter([]string{"nomad", "rpc", "request"}, 1)
   173  	}
   174  }
   175  
   176  // forward is used to forward to a remote region or to forward to the local leader
   177  // Returns a bool of if forwarding was performed, as well as any error
   178  func (s *Server) forward(method string, info structs.RPCInfo, args interface{}, reply interface{}) (bool, error) {
   179  	var firstCheck time.Time
   180  
   181  	region := info.RequestRegion()
   182  	if region == "" {
   183  		return true, fmt.Errorf("missing target RPC")
   184  	}
   185  
   186  	// Handle region forwarding
   187  	if region != s.config.Region {
   188  		err := s.forwardRegion(region, method, args, reply)
   189  		return true, err
   190  	}
   191  
   192  	// Check if we can allow a stale read
   193  	if info.IsRead() && info.AllowStaleRead() {
   194  		return false, nil
   195  	}
   196  
   197  CHECK_LEADER:
   198  	// Find the leader
   199  	isLeader, remoteServer := s.getLeader()
   200  
   201  	// Handle the case we are the leader
   202  	if isLeader {
   203  		return false, nil
   204  	}
   205  
   206  	// Handle the case of a known leader
   207  	if remoteServer != nil {
   208  		err := s.forwardLeader(remoteServer, method, args, reply)
   209  		return true, err
   210  	}
   211  
   212  	// Gate the request until there is a leader
   213  	if firstCheck.IsZero() {
   214  		firstCheck = time.Now()
   215  	}
   216  	if time.Now().Sub(firstCheck) < s.config.RPCHoldTimeout {
   217  		jitter := lib.RandomStagger(s.config.RPCHoldTimeout / jitterFraction)
   218  		select {
   219  		case <-time.After(jitter):
   220  			goto CHECK_LEADER
   221  		case <-s.shutdownCh:
   222  		}
   223  	}
   224  
   225  	// No leader found and hold time exceeded
   226  	return true, structs.ErrNoLeader
   227  }
   228  
   229  // getLeader returns if the current node is the leader, and if not
   230  // then it returns the leader which is potentially nil if the cluster
   231  // has not yet elected a leader.
   232  func (s *Server) getLeader() (bool, *serverParts) {
   233  	// Check if we are the leader
   234  	if s.IsLeader() {
   235  		return true, nil
   236  	}
   237  
   238  	// Get the leader
   239  	leader := s.raft.Leader()
   240  	if leader == "" {
   241  		return false, nil
   242  	}
   243  
   244  	// Lookup the server
   245  	s.peerLock.RLock()
   246  	server := s.localPeers[leader]
   247  	s.peerLock.RUnlock()
   248  
   249  	// Server could be nil
   250  	return false, server
   251  }
   252  
   253  // forwardLeader is used to forward an RPC call to the leader, or fail if no leader
   254  func (s *Server) forwardLeader(server *serverParts, method string, args interface{}, reply interface{}) error {
   255  	// Handle a missing server
   256  	if server == nil {
   257  		return structs.ErrNoLeader
   258  	}
   259  	return s.connPool.RPC(s.config.Region, server.Addr, server.MajorVersion, method, args, reply)
   260  }
   261  
   262  // forwardRegion is used to forward an RPC call to a remote region, or fail if no servers
   263  func (s *Server) forwardRegion(region, method string, args interface{}, reply interface{}) error {
   264  	// Bail if we can't find any servers
   265  	s.peerLock.RLock()
   266  	servers := s.peers[region]
   267  	if len(servers) == 0 {
   268  		s.peerLock.RUnlock()
   269  		s.logger.Printf("[WARN] nomad.rpc: RPC request for region '%s', no path found",
   270  			region)
   271  		return structs.ErrNoRegionPath
   272  	}
   273  
   274  	// Select a random addr
   275  	offset := rand.Intn(len(servers))
   276  	server := servers[offset]
   277  	s.peerLock.RUnlock()
   278  
   279  	// Forward to remote Nomad
   280  	metrics.IncrCounter([]string{"nomad", "rpc", "cross-region", region}, 1)
   281  	return s.connPool.RPC(region, server.Addr, server.MajorVersion, method, args, reply)
   282  }
   283  
   284  // raftApplyFuture is used to encode a message, run it through raft, and return the Raft future.
   285  func (s *Server) raftApplyFuture(t structs.MessageType, msg interface{}) (raft.ApplyFuture, error) {
   286  	buf, err := structs.Encode(t, msg)
   287  	if err != nil {
   288  		return nil, fmt.Errorf("Failed to encode request: %v", err)
   289  	}
   290  
   291  	// Warn if the command is very large
   292  	if n := len(buf); n > raftWarnSize {
   293  		s.logger.Printf("[WARN] nomad: Attempting to apply large raft entry (type %d) (%d bytes)", t, n)
   294  	}
   295  
   296  	future := s.raft.Apply(buf, enqueueLimit)
   297  	return future, nil
   298  }
   299  
   300  // raftApply is used to encode a message, run it through raft, and return
   301  // the FSM response along with any errors
   302  func (s *Server) raftApply(t structs.MessageType, msg interface{}) (interface{}, uint64, error) {
   303  	future, err := s.raftApplyFuture(t, msg)
   304  	if err != nil {
   305  		return nil, 0, err
   306  	}
   307  	if err := future.Error(); err != nil {
   308  		return nil, 0, err
   309  	}
   310  	return future.Response(), future.Index(), nil
   311  }
   312  
   313  // setQueryMeta is used to populate the QueryMeta data for an RPC call
   314  func (s *Server) setQueryMeta(m *structs.QueryMeta) {
   315  	if s.IsLeader() {
   316  		m.LastContact = 0
   317  		m.KnownLeader = true
   318  	} else {
   319  		m.LastContact = time.Now().Sub(s.raft.LastContact())
   320  		m.KnownLeader = (s.raft.Leader() != "")
   321  	}
   322  }
   323  
   324  // queryFn is used to perform a query operation. If a re-query is needed, the
   325  // passed-in watch set will be used to block for changes. The passed-in state
   326  // store should be used (vs. calling fsm.State()) since the given state store
   327  // will be correctly watched for changes if the state store is restored from
   328  // a snapshot.
   329  type queryFn func(memdb.WatchSet, *state.StateStore) error
   330  
   331  // blockingOptions is used to parameterize blockingRPC
   332  type blockingOptions struct {
   333  	queryOpts *structs.QueryOptions
   334  	queryMeta *structs.QueryMeta
   335  	run       queryFn
   336  }
   337  
   338  // blockingRPC is used for queries that need to wait for a
   339  // minimum index. This is used to block and wait for changes.
   340  func (s *Server) blockingRPC(opts *blockingOptions) error {
   341  	var timeout *time.Timer
   342  	var state *state.StateStore
   343  
   344  	// Fast path non-blocking
   345  	if opts.queryOpts.MinQueryIndex == 0 {
   346  		goto RUN_QUERY
   347  	}
   348  
   349  	// Restrict the max query time, and ensure there is always one
   350  	if opts.queryOpts.MaxQueryTime > maxQueryTime {
   351  		opts.queryOpts.MaxQueryTime = maxQueryTime
   352  	} else if opts.queryOpts.MaxQueryTime <= 0 {
   353  		opts.queryOpts.MaxQueryTime = defaultQueryTime
   354  	}
   355  
   356  	// Apply a small amount of jitter to the request
   357  	opts.queryOpts.MaxQueryTime += lib.RandomStagger(opts.queryOpts.MaxQueryTime / jitterFraction)
   358  
   359  	// Setup a query timeout
   360  	timeout = time.NewTimer(opts.queryOpts.MaxQueryTime)
   361  	defer timeout.Stop()
   362  
   363  RUN_QUERY:
   364  	// Update the query meta data
   365  	s.setQueryMeta(opts.queryMeta)
   366  
   367  	// Increment the rpc query counter
   368  	metrics.IncrCounter([]string{"nomad", "rpc", "query"}, 1)
   369  
   370  	// We capture the state store and its abandon channel but pass a snapshot to
   371  	// the blocking query function. We operate on the snapshot to allow separate
   372  	// calls to the state store not all wrapped within the same transaction.
   373  	state = s.fsm.State()
   374  	abandonCh := state.AbandonCh()
   375  	snap, _ := state.Snapshot()
   376  	stateSnap := &snap.StateStore
   377  
   378  	// We can skip all watch tracking if this isn't a blocking query.
   379  	var ws memdb.WatchSet
   380  	if opts.queryOpts.MinQueryIndex > 0 {
   381  		ws = memdb.NewWatchSet()
   382  
   383  		// This channel will be closed if a snapshot is restored and the
   384  		// whole state store is abandoned.
   385  		ws.Add(abandonCh)
   386  	}
   387  
   388  	// Block up to the timeout if we didn't see anything fresh.
   389  	err := opts.run(ws, stateSnap)
   390  
   391  	// Check for minimum query time
   392  	if err == nil && opts.queryOpts.MinQueryIndex > 0 && opts.queryMeta.Index <= opts.queryOpts.MinQueryIndex {
   393  		if expired := ws.Watch(timeout.C); !expired {
   394  			goto RUN_QUERY
   395  		}
   396  	}
   397  	return err
   398  }