open-nomad/nomad/rpc.go

815 lines
23 KiB
Go
Raw Normal View History

2015-06-01 15:49:10 +00:00
package nomad
2015-06-03 10:26:50 +00:00
import (
2017-08-31 00:45:32 +00:00
"context"
2015-06-03 10:26:50 +00:00
"crypto/tls"
"crypto/x509"
2018-01-15 22:48:53 +00:00
"errors"
2015-06-05 22:22:05 +00:00
"fmt"
2015-06-03 10:26:50 +00:00
"io"
2015-06-07 18:50:53 +00:00
"math/rand"
2015-06-03 10:26:50 +00:00
"net"
"net/rpc"
2015-06-03 10:26:50 +00:00
"strings"
2015-06-05 22:22:05 +00:00
"time"
2015-06-03 10:26:50 +00:00
2018-09-17 21:22:40 +00:00
golog "log"
metrics "github.com/armon/go-metrics"
"github.com/hashicorp/go-connlimit"
2018-09-15 23:23:13 +00:00
log "github.com/hashicorp/go-hclog"
memdb "github.com/hashicorp/go-memdb"
2018-09-15 23:23:13 +00:00
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/go-msgpack/codec"
2018-01-12 21:58:44 +00:00
"github.com/hashicorp/nomad/helper/pool"
"github.com/hashicorp/nomad/nomad/state"
2015-06-05 22:22:05 +00:00
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/nomad/structs/config"
"github.com/hashicorp/raft"
2015-06-03 10:26:50 +00:00
"github.com/hashicorp/yamux"
)
2015-06-05 22:22:05 +00:00
const (
2015-06-07 18:50:53 +00:00
// maxQueryTime is used to bound the limit of a blocking query
maxQueryTime = 300 * time.Second
// defaultQueryTime is the amount of time we block waiting for a change
// if no time is specified. Previously we would wait the maxQueryTime.
defaultQueryTime = 300 * time.Second
2015-06-05 22:22:05 +00:00
// Warn if the Raft command is larger than this.
// If it's over 1MB something is probably being abusive.
raftWarnSize = 1024 * 1024
// enqueueLimit caps how long we will wait to enqueue
// a new Raft command. Something is probably wrong if this
// value is ever reached. However, it prevents us from blocking
// the requesting goroutine forever.
enqueueLimit = 30 * time.Second
)
2018-09-15 23:23:13 +00:00
type rpcHandler struct {
*Server
// connLimiter is used to limit the number of RPC connections per
// remote address. It is distinct from the HTTP connection limit.
//
// nil if limiting is disabled
connLimiter *connlimit.Limiter
connLimit int
// streamLimiter is used to limit the number of *streaming* RPC
// connections per remote address. It is lower than the overall
// connection limit to ensure their are free connections for Raft and
// other RPCs.
streamLimiter *connlimit.Limiter
streamLimit int
2018-12-12 23:10:24 +00:00
logger log.Logger
gologger *golog.Logger
2018-09-15 23:23:13 +00:00
}
func newRpcHandler(s *Server) *rpcHandler {
logger := s.logger.NamedIntercept("rpc")
r := rpcHandler{
Server: s,
connLimit: s.config.RPCMaxConnsPerClient,
logger: logger,
gologger: logger.StandardLoggerIntercept(&log.StandardLoggerOptions{InferLevels: true}),
}
// Setup connection limits
if r.connLimit > 0 {
r.connLimiter = connlimit.NewLimiter(connlimit.Config{
MaxConnsPerClientIP: r.connLimit,
})
r.streamLimit = r.connLimit - config.LimitsNonStreamingConnsPerClient
r.streamLimiter = connlimit.NewLimiter(connlimit.Config{
MaxConnsPerClientIP: r.streamLimit,
})
2018-09-15 23:23:13 +00:00
}
return &r
2018-09-15 23:23:13 +00:00
}
2018-01-04 00:00:55 +00:00
// RPCContext provides metadata about the RPC connection.
type RPCContext struct {
2018-01-05 21:50:04 +00:00
// Conn exposes the raw connection.
Conn net.Conn
2018-01-04 00:00:55 +00:00
// Session exposes the multiplexed connection session.
Session *yamux.Session
// TLS marks whether the RPC is over a TLS based connection
TLS bool
// VerifiedChains is is the Verified certificates presented by the incoming
// connection.
VerifiedChains [][]*x509.Certificate
2018-01-05 21:50:04 +00:00
// NodeID marks the NodeID that initiated the connection.
NodeID string
2018-01-04 00:00:55 +00:00
}
2015-06-03 10:26:50 +00:00
// listen is used to listen for incoming RPC connections
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) listen(ctx context.Context) {
defer close(r.listenerCh)
2018-12-12 23:10:24 +00:00
var acceptLoopDelay time.Duration
2015-06-03 10:26:50 +00:00
for {
select {
case <-ctx.Done():
2018-09-15 23:23:13 +00:00
r.logger.Info("closing server RPC connection")
return
default:
}
2015-06-03 10:26:50 +00:00
// Accept a connection
2018-09-15 23:23:13 +00:00
conn, err := r.rpcListener.Accept()
2015-06-03 10:26:50 +00:00
if err != nil {
2018-09-15 23:23:13 +00:00
if r.shutdown {
2015-06-03 10:26:50 +00:00
return
}
2018-12-12 23:10:24 +00:00
r.handleAcceptErr(ctx, err, &acceptLoopDelay)
2015-06-03 10:26:50 +00:00
continue
}
2018-12-12 19:09:06 +00:00
// No error, reset loop delay
2018-12-12 23:10:24 +00:00
acceptLoopDelay = 0
2015-06-03 10:26:50 +00:00
// Apply per-connection limits (if enabled) *prior* to launching
// goroutine to block further Accept()s until limits are checked.
if r.connLimiter != nil {
free, err := r.connLimiter.Accept(conn)
if err != nil {
r.logger.Error("rejecting client for exceeding maximum RPC connections",
"remote_addr", conn.RemoteAddr(), "limit", r.connLimit)
conn.Close()
continue
}
// Wrap the connection so that conn.Close calls free() as well.
// This is required for libraries like raft which handoff the
// net.Conn to another goroutine and therefore can't be tracked
// within this func.
conn = connlimit.Wrap(conn, free)
}
2018-09-15 23:23:13 +00:00
go r.handleConn(ctx, conn, &RPCContext{Conn: conn})
2015-06-03 10:26:50 +00:00
metrics.IncrCounter([]string{"nomad", "rpc", "accept_conn"}, 1)
}
}
2018-12-12 23:10:24 +00:00
// handleAcceptErr sleeps to avoid spamming the log,
// with a maximum delay according to whether or not the error is temporary
func (r *rpcHandler) handleAcceptErr(ctx context.Context, err error, loopDelay *time.Duration) {
const baseDelay = 5 * time.Millisecond
const maxDelayPerm = 5 * time.Second
const maxDelayTemp = 1 * time.Second
2018-12-12 18:52:06 +00:00
2018-12-12 23:10:24 +00:00
if *loopDelay == 0 {
*loopDelay = baseDelay
2018-12-12 18:52:06 +00:00
} else {
2018-12-12 23:10:24 +00:00
*loopDelay *= 2
2018-12-12 18:52:06 +00:00
}
2018-12-12 23:10:24 +00:00
2018-12-12 18:52:06 +00:00
temporaryError := false
if ne, ok := err.(net.Error); ok && ne.Temporary() {
temporaryError = true
}
2018-12-12 23:10:24 +00:00
if temporaryError && *loopDelay > maxDelayTemp {
*loopDelay = maxDelayTemp
} else if *loopDelay > maxDelayPerm {
*loopDelay = maxDelayPerm
2018-12-12 18:52:06 +00:00
}
2018-12-12 23:10:24 +00:00
r.logger.Error("failed to accept RPC conn", "error", err, "delay", *loopDelay)
2018-12-12 18:52:06 +00:00
select {
case <-ctx.Done():
2018-12-12 23:10:24 +00:00
case <-time.After(*loopDelay):
2018-12-12 18:52:06 +00:00
}
}
2015-06-03 10:26:50 +00:00
// handleConn is used to determine if this is a Raft or
// Nomad type RPC connection and invoke the correct handler
//
// **Cannot** use defer conn.Close in this method because the Raft handler uses
// the conn beyond the scope of this func.
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) handleConn(ctx context.Context, conn net.Conn, rpcCtx *RPCContext) {
// Limit how long an unauthenticated client can hold the connection
// open before they send the magic byte.
if !rpcCtx.TLS && r.config.RPCHandshakeTimeout > 0 {
conn.SetDeadline(time.Now().Add(r.config.RPCHandshakeTimeout))
}
2015-06-03 10:26:50 +00:00
// Read a single byte
buf := make([]byte, 1)
if _, err := conn.Read(buf); err != nil {
if err != io.EOF {
2018-09-15 23:23:13 +00:00
r.logger.Error("failed to read first RPC byte", "error", err)
2015-06-03 10:26:50 +00:00
}
conn.Close()
return
}
// Reset the deadline as we aren't sure what is expected next - it depends on
// the protocol.
if !rpcCtx.TLS && r.config.RPCHandshakeTimeout > 0 {
conn.SetDeadline(time.Time{})
}
// Enforce TLS if EnableRPC is set
2018-09-15 23:23:13 +00:00
if r.config.TLSConfig.EnableRPC && !rpcCtx.TLS && pool.RPCType(buf[0]) != pool.RpcTLS {
if !r.config.TLSConfig.RPCUpgradeMode {
r.logger.Warn("non-TLS connection attempted with RequireTLS set", "remote_addr", conn.RemoteAddr())
conn.Close()
return
}
2015-06-03 10:26:50 +00:00
}
// Switch on the byte
2018-01-12 21:58:44 +00:00
switch pool.RPCType(buf[0]) {
case pool.RpcNomad:
2018-01-04 00:00:55 +00:00
// Create an RPC Server and handle the request
server := rpc.NewServer()
2018-09-15 23:23:13 +00:00
r.setupRpcServer(server, rpcCtx)
r.handleNomadConn(ctx, conn, server)
2015-06-03 10:26:50 +00:00
2018-01-05 21:50:04 +00:00
// Remove any potential mapping between a NodeID to this connection and
// close the underlying connection.
2018-09-15 23:23:13 +00:00
r.removeNodeConn(rpcCtx)
2018-01-05 21:50:04 +00:00
2018-01-12 21:58:44 +00:00
case pool.RpcRaft:
2015-06-03 10:26:50 +00:00
metrics.IncrCounter([]string{"nomad", "rpc", "raft_handoff"}, 1)
2018-09-15 23:23:13 +00:00
r.raftLayer.Handoff(ctx, conn)
2015-06-03 10:26:50 +00:00
2018-01-12 21:58:44 +00:00
case pool.RpcMultiplex:
2018-09-15 23:23:13 +00:00
r.handleMultiplex(ctx, conn, rpcCtx)
2015-06-03 10:26:50 +00:00
2018-01-12 21:58:44 +00:00
case pool.RpcTLS:
2018-09-15 23:23:13 +00:00
if r.rpcTLS == nil {
r.logger.Warn("TLS connection attempted, server not configured for TLS")
2015-06-03 10:26:50 +00:00
conn.Close()
return
}
// Don't allow malicious client to create TLS-in-TLS forever.
if rpcCtx.TLS {
r.logger.Error("TLS connection attempting to establish inner TLS connection", "remote_addr", conn.RemoteAddr())
conn.Close()
return
}
2018-09-15 23:23:13 +00:00
conn = tls.Server(conn, r.rpcTLS)
2018-01-04 00:00:55 +00:00
2018-01-05 00:33:07 +00:00
// Force a handshake so we can get information about the TLS connection
// state.
tlsConn, ok := conn.(*tls.Conn)
if !ok {
2018-09-15 23:23:13 +00:00
r.logger.Error("expected TLS connection", "got", log.Fmt("%T", conn))
2018-01-05 00:33:07 +00:00
conn.Close()
return
}
// Enforce handshake timeout during TLS handshake to prevent
// unauthenticated users from holding connections open
// indefinitely.
if r.config.RPCHandshakeTimeout > 0 {
tlsConn.SetDeadline(time.Now().Add(r.config.RPCHandshakeTimeout))
}
2018-01-05 00:33:07 +00:00
if err := tlsConn.Handshake(); err != nil {
2018-09-15 23:23:13 +00:00
r.logger.Warn("failed TLS handshake", "remote_addr", tlsConn.RemoteAddr(), "error", err)
2018-01-05 00:33:07 +00:00
conn.Close()
return
}
// Reset the deadline as unauthenticated users have now been rejected.
if r.config.RPCHandshakeTimeout > 0 {
tlsConn.SetDeadline(time.Time{})
}
2018-01-04 00:00:55 +00:00
// Update the connection context with the fact that the connection is
// using TLS
rpcCtx.TLS = true
// Store the verified chains so they can be inspected later.
2018-01-05 00:33:07 +00:00
state := tlsConn.ConnectionState()
rpcCtx.VerifiedChains = state.VerifiedChains
2018-01-05 00:33:07 +00:00
2018-09-15 23:23:13 +00:00
r.handleConn(ctx, conn, rpcCtx)
2015-06-03 10:26:50 +00:00
case pool.RpcStreaming:
// Apply a lower limit to streaming RPCs to avoid denial of
// service by repeatedly starting streaming RPCs.
//
// TODO Remove once MultiplexV2 is used.
if r.streamLimiter != nil {
free, err := r.streamLimiter.Accept(conn)
if err != nil {
r.logger.Error("rejecting client for exceeding maximum streaming RPC connections",
"remote_addr", conn.RemoteAddr(), "stream_limit", r.streamLimit)
conn.Close()
return
}
defer free()
}
2018-09-15 23:23:13 +00:00
r.handleStreamingConn(conn)
case pool.RpcMultiplexV2:
2018-09-15 23:23:13 +00:00
r.handleMultiplexV2(ctx, conn, rpcCtx)
2015-06-03 10:26:50 +00:00
default:
2018-09-15 23:23:13 +00:00
r.logger.Error("unrecognized RPC byte", "byte", buf[0])
2015-06-03 10:26:50 +00:00
conn.Close()
return
}
}
// handleMultiplex is used to multiplex a single incoming connection
// using the Yamux multiplexer
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) handleMultiplex(ctx context.Context, conn net.Conn, rpcCtx *RPCContext) {
2018-01-05 21:50:04 +00:00
defer func() {
// Remove any potential mapping between a NodeID to this connection and
// close the underlying connection.
2018-09-15 23:23:13 +00:00
r.removeNodeConn(rpcCtx)
2018-01-05 21:50:04 +00:00
conn.Close()
}()
2015-06-03 10:26:50 +00:00
conf := yamux.DefaultConfig()
2018-09-17 21:22:40 +00:00
conf.LogOutput = nil
conf.Logger = r.gologger
2018-02-14 21:08:31 +00:00
server, err := yamux.Server(conn, conf)
if err != nil {
2018-09-15 23:23:13 +00:00
r.logger.Error("multiplex failed to create yamux server", "error", err)
2018-02-14 21:08:31 +00:00
return
}
2018-01-04 00:00:55 +00:00
// Update the context to store the yamux session
rpcCtx.Session = server
// Create the RPC server for this connection
rpcServer := rpc.NewServer()
2018-09-15 23:23:13 +00:00
r.setupRpcServer(rpcServer, rpcCtx)
2018-01-04 00:00:55 +00:00
2015-06-03 10:26:50 +00:00
for {
// stop handling connections if context was cancelled
if ctx.Err() != nil {
return
}
2015-06-03 10:26:50 +00:00
sub, err := server.Accept()
if err != nil {
if err != io.EOF {
2018-09-15 23:23:13 +00:00
r.logger.Error("multiplex conn accept failed", "error", err)
2015-06-03 10:26:50 +00:00
}
return
}
2018-09-15 23:23:13 +00:00
go r.handleNomadConn(ctx, sub, rpcServer)
2015-06-03 10:26:50 +00:00
}
}
// handleNomadConn is used to service a single Nomad RPC connection
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) handleNomadConn(ctx context.Context, conn net.Conn, server *rpc.Server) {
2015-06-03 10:26:50 +00:00
defer conn.Close()
2018-01-12 21:58:44 +00:00
rpcCodec := pool.NewServerCodec(conn)
2015-06-03 10:26:50 +00:00
for {
select {
case <-ctx.Done():
2018-09-15 23:23:13 +00:00
r.logger.Info("closing server RPC connection")
return
2018-09-15 23:23:13 +00:00
case <-r.shutdownCh:
2015-06-03 10:26:50 +00:00
return
default:
}
2018-01-04 00:00:55 +00:00
if err := server.ServeRequest(rpcCodec); err != nil {
2015-06-03 10:26:50 +00:00
if err != io.EOF && !strings.Contains(err.Error(), "closed") {
2018-09-15 23:23:13 +00:00
r.logger.Error("RPC error", "error", err, "connection", conn)
2015-06-03 10:26:50 +00:00
metrics.IncrCounter([]string{"nomad", "rpc", "request_error"}, 1)
}
return
}
metrics.IncrCounter([]string{"nomad", "rpc", "request"}, 1)
}
}
2015-06-05 22:22:05 +00:00
// handleStreamingConn is used to handle a single Streaming Nomad RPC connection.
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) handleStreamingConn(conn net.Conn) {
defer conn.Close()
// Decode the header
var header structs.StreamingRpcHeader
decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
if err := decoder.Decode(&header); err != nil {
if err != io.EOF && !strings.Contains(err.Error(), "closed") {
2018-09-15 23:23:13 +00:00
r.logger.Error("streaming RPC error", "error", err, "connection", conn)
metrics.IncrCounter([]string{"nomad", "streaming_rpc", "request_error"}, 1)
}
return
}
ack := structs.StreamingRpcAck{}
2018-09-15 23:23:13 +00:00
handler, err := r.streamingRpcs.GetHandler(header.Method)
if err != nil {
2018-09-15 23:23:13 +00:00
r.logger.Error("streaming RPC error", "error", err, "connection", conn)
metrics.IncrCounter([]string{"nomad", "streaming_rpc", "request_error"}, 1)
ack.Error = err.Error()
}
// Send the acknowledgement
encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
if err := encoder.Encode(ack); err != nil {
conn.Close()
return
}
if ack.Error != "" {
return
}
// Invoke the handler
metrics.IncrCounter([]string{"nomad", "streaming_rpc", "request"}, 1)
handler(conn)
}
// handleMultiplexV2 is used to multiplex a single incoming connection
// using the Yamux multiplexer. Version 2 handling allows a single connection to
// switch streams between regulars RPCs and Streaming RPCs.
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) handleMultiplexV2(ctx context.Context, conn net.Conn, rpcCtx *RPCContext) {
defer func() {
// Remove any potential mapping between a NodeID to this connection and
// close the underlying connection.
2018-09-15 23:23:13 +00:00
r.removeNodeConn(rpcCtx)
conn.Close()
}()
conf := yamux.DefaultConfig()
2018-09-17 21:22:40 +00:00
conf.LogOutput = nil
conf.Logger = r.gologger
2018-02-14 21:08:31 +00:00
server, err := yamux.Server(conn, conf)
if err != nil {
2018-09-15 23:23:13 +00:00
r.logger.Error("multiplex_v2 failed to create yamux server", "error", err)
2018-02-14 21:08:31 +00:00
return
}
// Update the context to store the yamux session
2018-02-15 23:03:12 +00:00
rpcCtx.Session = server
// Create the RPC server for this connection
rpcServer := rpc.NewServer()
2018-09-15 23:23:13 +00:00
r.setupRpcServer(rpcServer, rpcCtx)
for {
// stop handling connections if context was cancelled
if ctx.Err() != nil {
return
}
// Accept a new stream
sub, err := server.Accept()
if err != nil {
if err != io.EOF {
2018-09-15 23:23:13 +00:00
r.logger.Error("multiplex_v2 conn accept failed", "error", err)
}
return
}
// Read a single byte
buf := make([]byte, 1)
if _, err := sub.Read(buf); err != nil {
if err != io.EOF {
2018-09-15 23:23:13 +00:00
r.logger.Error("multiplex_v2 failed to read first byte", "error", err)
}
return
}
// Determine which handler to use
switch pool.RPCType(buf[0]) {
case pool.RpcNomad:
2018-09-15 23:23:13 +00:00
go r.handleNomadConn(ctx, sub, rpcServer)
case pool.RpcStreaming:
2018-09-15 23:23:13 +00:00
go r.handleStreamingConn(sub)
default:
2018-09-15 23:23:13 +00:00
r.logger.Error("multiplex_v2 unrecognized first RPC byte", "byte", buf[0])
return
}
}
}
2015-06-07 18:50:53 +00:00
// forward is used to forward to a remote region or to forward to the local leader
// Returns a bool of if forwarding was performed, as well as any error
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) forward(method string, info structs.RPCInfo, args interface{}, reply interface{}) (bool, error) {
var firstCheck time.Time
2015-06-07 18:50:53 +00:00
region := info.RequestRegion()
if region == "" {
return true, fmt.Errorf("missing region for target RPC")
}
// Handle region forwarding
2018-09-15 23:23:13 +00:00
if region != r.config.Region {
// Mark that we are forwarding the RPC
info.SetForwarded()
2018-09-15 23:23:13 +00:00
err := r.forwardRegion(region, method, args, reply)
2015-06-07 18:50:53 +00:00
return true, err
}
// Check if we can allow a stale read
if info.IsRead() && info.AllowStaleRead() {
return false, nil
}
CHECK_LEADER:
// Find the leader
2018-09-15 23:23:13 +00:00
isLeader, remoteServer := r.getLeader()
// Handle the case we are the leader
if isLeader && r.Server.isReadyForConsistentReads() {
return false, nil
}
// Handle the case of a known leader
if remoteServer != nil {
// Mark that we are forwarding the RPC
info.SetForwarded()
2018-09-15 23:23:13 +00:00
err := r.forwardLeader(remoteServer, method, args, reply)
2015-06-07 18:50:53 +00:00
return true, err
}
// Gate the request until there is a leader
if firstCheck.IsZero() {
firstCheck = time.Now()
}
2018-09-15 23:23:13 +00:00
if time.Now().Sub(firstCheck) < r.config.RPCHoldTimeout {
jitter := lib.RandomStagger(r.config.RPCHoldTimeout / structs.JitterFraction)
select {
case <-time.After(jitter):
goto CHECK_LEADER
2018-09-15 23:23:13 +00:00
case <-r.shutdownCh:
}
}
// hold time exceeeded without being ready to respond
if isLeader {
return true, structs.ErrNotReadyForConsistentReads
}
return true, structs.ErrNoLeader
2015-06-07 18:50:53 +00:00
}
// getLeader returns if the current node is the leader, and if not
// then it returns the leader which is potentially nil if the cluster
// has not yet elected a leader.
func (s *Server) getLeader() (bool, *serverParts) {
// Check if we are the leader
if s.IsLeader() {
return true, nil
}
2015-06-07 18:50:53 +00:00
// Get the leader
leader := s.raft.Leader()
if leader == "" {
return false, nil
2015-06-07 18:50:53 +00:00
}
// Lookup the server
s.peerLock.RLock()
server := s.localPeers[leader]
s.peerLock.RUnlock()
// Server could be nil
return false, server
}
// forwardLeader is used to forward an RPC call to the leader, or fail if no leader
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) forwardLeader(server *serverParts, method string, args interface{}, reply interface{}) error {
2015-06-07 18:50:53 +00:00
// Handle a missing server
if server == nil {
return structs.ErrNoLeader
}
2018-09-15 23:23:13 +00:00
return r.connPool.RPC(r.config.Region, server.Addr, server.MajorVersion, method, args, reply)
2015-06-07 18:50:53 +00:00
}
2018-01-15 22:48:53 +00:00
// forwardServer is used to forward an RPC call to a particular server
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) forwardServer(server *serverParts, method string, args interface{}, reply interface{}) error {
2018-01-15 22:48:53 +00:00
// Handle a missing server
if server == nil {
return errors.New("must be given a valid server address")
}
2018-09-15 23:23:13 +00:00
return r.connPool.RPC(r.config.Region, server.Addr, server.MajorVersion, method, args, reply)
2018-01-15 22:48:53 +00:00
}
2015-06-07 18:50:53 +00:00
// forwardRegion is used to forward an RPC call to a remote region, or fail if no servers
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) forwardRegion(region, method string, args interface{}, reply interface{}) error {
2015-06-07 18:50:53 +00:00
// Bail if we can't find any servers
2018-09-15 23:23:13 +00:00
r.peerLock.RLock()
servers := r.peers[region]
2015-06-07 18:50:53 +00:00
if len(servers) == 0 {
2018-09-15 23:23:13 +00:00
r.peerLock.RUnlock()
r.logger.Warn("no path found to region", "region", region)
2015-06-07 18:50:53 +00:00
return structs.ErrNoRegionPath
}
// Select a random addr
offset := rand.Intn(len(servers))
2015-06-07 18:50:53 +00:00
server := servers[offset]
2018-09-15 23:23:13 +00:00
r.peerLock.RUnlock()
2015-06-07 18:50:53 +00:00
// Forward to remote Nomad
metrics.IncrCounter([]string{"nomad", "rpc", "cross-region", region}, 1)
2018-09-15 23:23:13 +00:00
return r.connPool.RPC(region, server.Addr, server.MajorVersion, method, args, reply)
2015-06-07 18:50:53 +00:00
}
func (r *rpcHandler) getServer(region, serverID string) (*serverParts, error) {
// Bail if we can't find any servers
r.peerLock.RLock()
defer r.peerLock.RUnlock()
servers := r.peers[region]
if len(servers) == 0 {
r.logger.Warn("no path found to region", "region", region)
return nil, structs.ErrNoRegionPath
}
// Lookup server by id or name
for _, server := range servers {
if server.Name == serverID || server.ID == serverID {
return server, nil
}
}
return nil, fmt.Errorf("unknown Nomad server %s", serverID)
}
2018-01-30 06:01:42 +00:00
// streamingRpc creates a connection to the given server and conducts the
// initial handshake, returning the connection or an error. It is the callers
// responsibility to close the connection if there is no returned error.
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) streamingRpc(server *serverParts, method string) (net.Conn, error) {
c, err := r.connPool.StreamingRPC(r.config.Region, server.Addr, server.MajorVersion)
2018-01-30 06:01:42 +00:00
if err != nil {
return nil, err
}
return r.streamingRpcImpl(c, method)
}
// streamingRpcImpl takes a pre-established connection to a server and conducts
// the handshake to establish a streaming RPC for the given method. If an error
// is returned, the underlying connection has been closed. Otherwise it is
// assumed that the connection has been hijacked by the RPC method.
func (r *rpcHandler) streamingRpcImpl(conn net.Conn, method string) (net.Conn, error) {
2018-01-30 06:01:42 +00:00
// Send the header
encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
2018-01-30 06:01:42 +00:00
header := structs.StreamingRpcHeader{
Method: method,
}
if err := encoder.Encode(header); err != nil {
conn.Close()
return nil, err
2018-01-30 06:01:42 +00:00
}
// Wait for the acknowledgement
var ack structs.StreamingRpcAck
if err := decoder.Decode(&ack); err != nil {
conn.Close()
return nil, err
}
if ack.Error != "" {
conn.Close()
return nil, errors.New(ack.Error)
}
return conn, nil
2018-01-30 06:01:42 +00:00
}
// raftApplyFuture is used to encode a message, run it through raft, and return the Raft future.
func (s *Server) raftApplyFuture(t structs.MessageType, msg interface{}) (raft.ApplyFuture, error) {
buf, err := structs.Encode(t, msg)
2015-06-05 22:22:05 +00:00
if err != nil {
return nil, fmt.Errorf("Failed to encode request: %v", err)
2015-06-05 22:22:05 +00:00
}
// Warn if the command is very large
if n := len(buf); n > raftWarnSize {
2018-09-15 23:23:13 +00:00
s.logger.Warn("attempting to apply large raft entry", "raft_type", t, "bytes", n)
2015-06-05 22:22:05 +00:00
}
future := s.raft.Apply(buf, enqueueLimit)
return future, nil
}
2015-06-05 22:22:05 +00:00
2017-06-28 22:35:52 +00:00
// raftApplyFn is the function signature for applying a msg to Raft
type raftApplyFn func(t structs.MessageType, msg interface{}) (interface{}, uint64, error)
// raftApply is used to encode a message, run it through raft, and return
// the FSM response along with any errors
func (s *Server) raftApply(t structs.MessageType, msg interface{}) (interface{}, uint64, error) {
future, err := s.raftApplyFuture(t, msg)
if err != nil {
return nil, 0, err
}
if err := future.Error(); err != nil {
return nil, 0, err
}
2015-07-06 20:34:32 +00:00
return future.Response(), future.Index(), nil
2015-06-05 22:22:05 +00:00
}
2015-07-06 21:23:15 +00:00
// setQueryMeta is used to populate the QueryMeta data for an RPC call
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) setQueryMeta(m *structs.QueryMeta) {
if r.IsLeader() {
2015-07-06 21:23:15 +00:00
m.LastContact = 0
m.KnownLeader = true
} else {
2018-09-15 23:23:13 +00:00
m.LastContact = time.Now().Sub(r.raft.LastContact())
m.KnownLeader = (r.raft.Leader() != "")
2015-07-06 21:23:15 +00:00
}
}
// queryFn is used to perform a query operation. If a re-query is needed, the
// passed-in watch set will be used to block for changes. The passed-in state
// store should be used (vs. calling fsm.State()) since the given state store
// will be correctly watched for changes if the state store is restored from
// a snapshot.
type queryFn func(memdb.WatchSet, *state.StateStore) error
// blockingOptions is used to parameterize blockingRPC
type blockingOptions struct {
queryOpts *structs.QueryOptions
queryMeta *structs.QueryMeta
run queryFn
}
// blockingRPC is used for queries that need to wait for a
// minimum index. This is used to block and wait for changes.
2018-09-15 23:23:13 +00:00
func (r *rpcHandler) blockingRPC(opts *blockingOptions) error {
2017-08-31 00:45:32 +00:00
ctx := context.Background()
var cancel context.CancelFunc
var state *state.StateStore
// Fast path non-blocking
if opts.queryOpts.MinQueryIndex == 0 {
goto RUN_QUERY
}
// Restrict the max query time, and ensure there is always one
if opts.queryOpts.MaxQueryTime > maxQueryTime {
opts.queryOpts.MaxQueryTime = maxQueryTime
} else if opts.queryOpts.MaxQueryTime <= 0 {
opts.queryOpts.MaxQueryTime = defaultQueryTime
}
// Apply a small amount of jitter to the request
2018-01-26 02:15:21 +00:00
opts.queryOpts.MaxQueryTime += lib.RandomStagger(opts.queryOpts.MaxQueryTime / structs.JitterFraction)
// Setup a query timeout
2017-08-31 20:03:35 +00:00
ctx, cancel = context.WithTimeout(context.Background(), opts.queryOpts.MaxQueryTime)
2017-08-31 00:45:32 +00:00
defer cancel()
RUN_QUERY:
// Update the query meta data
2018-09-15 23:23:13 +00:00
r.setQueryMeta(opts.queryMeta)
// Increment the rpc query counter
metrics.IncrCounter([]string{"nomad", "rpc", "query"}, 1)
// We capture the state store and its abandon channel but pass a snapshot to
// the blocking query function. We operate on the snapshot to allow separate
// calls to the state store not all wrapped within the same transaction.
2018-09-15 23:23:13 +00:00
state = r.fsm.State()
abandonCh := state.AbandonCh()
2017-02-08 06:10:33 +00:00
snap, _ := state.Snapshot()
stateSnap := &snap.StateStore
// We can skip all watch tracking if this isn't a blocking query.
var ws memdb.WatchSet
2017-02-08 04:31:23 +00:00
if opts.queryOpts.MinQueryIndex > 0 {
ws = memdb.NewWatchSet()
// This channel will be closed if a snapshot is restored and the
// whole state store is abandoned.
ws.Add(abandonCh)
}
// Block up to the timeout if we didn't see anything fresh.
err := opts.run(ws, stateSnap)
// Check for minimum query time
if err == nil && opts.queryOpts.MinQueryIndex > 0 && opts.queryMeta.Index <= opts.queryOpts.MinQueryIndex {
2017-09-01 16:53:09 +00:00
if err := ws.WatchCtx(ctx); err == nil {
goto RUN_QUERY
}
}
return err
}