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 }