github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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 "github.com/hashicorp/net-rpc-msgpackrpc" 16 "github.com/hashicorp/nomad/nomad/state" 17 "github.com/hashicorp/nomad/nomad/structs" 18 "github.com/hashicorp/nomad/nomad/watch" 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 VerifyIncoming is set 101 if s.config.RequireTLS && !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 // blockingOptions is used to parameterize blockingRPC 325 type blockingOptions struct { 326 queryOpts *structs.QueryOptions 327 queryMeta *structs.QueryMeta 328 watch watch.Items 329 run func() error 330 } 331 332 // blockingRPC is used for queries that need to wait for a 333 // minimum index. This is used to block and wait for changes. 334 func (s *Server) blockingRPC(opts *blockingOptions) error { 335 var timeout *time.Timer 336 var notifyCh chan struct{} 337 var state *state.StateStore 338 339 // Fast path non-blocking 340 if opts.queryOpts.MinQueryIndex == 0 { 341 goto RUN_QUERY 342 } 343 344 // Restrict the max query time, and ensure there is always one 345 if opts.queryOpts.MaxQueryTime > maxQueryTime { 346 opts.queryOpts.MaxQueryTime = maxQueryTime 347 } else if opts.queryOpts.MaxQueryTime <= 0 { 348 opts.queryOpts.MaxQueryTime = defaultQueryTime 349 } 350 351 // Apply a small amount of jitter to the request 352 opts.queryOpts.MaxQueryTime += lib.RandomStagger(opts.queryOpts.MaxQueryTime / jitterFraction) 353 354 // Setup a query timeout 355 timeout = time.NewTimer(opts.queryOpts.MaxQueryTime) 356 357 // Setup the notify channel 358 notifyCh = make(chan struct{}, 1) 359 360 // Ensure we tear down any watchers on return 361 state = s.fsm.State() 362 defer func() { 363 timeout.Stop() 364 state.StopWatch(opts.watch, notifyCh) 365 }() 366 367 REGISTER_NOTIFY: 368 // Register the notification channel. This may be done 369 // multiple times if we have not reached the target wait index. 370 state.Watch(opts.watch, notifyCh) 371 372 RUN_QUERY: 373 // Update the query meta data 374 s.setQueryMeta(opts.queryMeta) 375 376 // Run the query function 377 metrics.IncrCounter([]string{"nomad", "rpc", "query"}, 1) 378 err := opts.run() 379 380 // Check for minimum query time 381 if err == nil && opts.queryOpts.MinQueryIndex > 0 && opts.queryMeta.Index <= opts.queryOpts.MinQueryIndex { 382 select { 383 case <-notifyCh: 384 goto REGISTER_NOTIFY 385 case <-timeout.C: 386 } 387 } 388 return err 389 }