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 }