github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/client_rpc.go (about) 1 package nomad 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "time" 8 9 multierror "github.com/hashicorp/go-multierror" 10 msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc" 11 "github.com/hashicorp/nomad/helper/pool" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/yamux" 14 "github.com/ugorji/go/codec" 15 ) 16 17 // nodeConnState is used to track connection information about a Nomad Client. 18 type nodeConnState struct { 19 // Session holds the multiplexed yamux Session for dialing back. 20 Session *yamux.Session 21 22 // Established is when the connection was established. 23 Established time.Time 24 25 // Ctx is the full RPC context 26 Ctx *RPCContext 27 } 28 29 // getNodeConn returns the connection to the given node and whether it exists. 30 func (s *Server) getNodeConn(nodeID string) (*nodeConnState, bool) { 31 s.nodeConnsLock.RLock() 32 defer s.nodeConnsLock.RUnlock() 33 conns, ok := s.nodeConns[nodeID] 34 if !ok { 35 return nil, false 36 } 37 38 // Return the latest conn 39 var state *nodeConnState 40 for _, conn := range conns { 41 if state == nil || state.Established.Before(conn.Established) { 42 state = conn 43 } 44 } 45 46 // Shouldn't happen but rather be safe 47 if state == nil { 48 s.logger.Printf("[WARN] nomad.client_rpc: node %q exists in node connection map without any connection", nodeID) 49 return nil, false 50 } 51 52 return state, ok 53 } 54 55 // connectedNodes returns the set of nodes we have a connection with. 56 func (s *Server) connectedNodes() map[string]time.Time { 57 s.nodeConnsLock.RLock() 58 defer s.nodeConnsLock.RUnlock() 59 nodes := make(map[string]time.Time, len(s.nodeConns)) 60 for nodeID, conns := range s.nodeConns { 61 for _, conn := range conns { 62 if nodes[nodeID].Before(conn.Established) { 63 nodes[nodeID] = conn.Established 64 } 65 } 66 } 67 return nodes 68 } 69 70 // addNodeConn adds the mapping between a node and its session. 71 func (s *Server) addNodeConn(ctx *RPCContext) { 72 // Hotpath the no-op 73 if ctx == nil || ctx.NodeID == "" { 74 return 75 } 76 77 s.nodeConnsLock.Lock() 78 defer s.nodeConnsLock.Unlock() 79 80 // Capture the tracked connections so far 81 currentConns := s.nodeConns[ctx.NodeID] 82 83 // Check if we already have the connection. If we do, just update the 84 // establish time. 85 for _, c := range currentConns { 86 if c.Ctx.Conn.LocalAddr().String() == ctx.Conn.LocalAddr().String() && 87 c.Ctx.Conn.RemoteAddr().String() == ctx.Conn.RemoteAddr().String() { 88 c.Established = time.Now() 89 return 90 } 91 } 92 93 // Add the new conn 94 s.nodeConns[ctx.NodeID] = append(s.nodeConns[ctx.NodeID], &nodeConnState{ 95 Session: ctx.Session, 96 Established: time.Now(), 97 Ctx: ctx, 98 }) 99 } 100 101 // removeNodeConn removes the mapping between a node and its session. 102 func (s *Server) removeNodeConn(ctx *RPCContext) { 103 // Hotpath the no-op 104 if ctx == nil || ctx.NodeID == "" { 105 return 106 } 107 108 s.nodeConnsLock.Lock() 109 defer s.nodeConnsLock.Unlock() 110 conns, ok := s.nodeConns[ctx.NodeID] 111 if !ok { 112 return 113 } 114 115 // It is important that we check that the connection being removed is the 116 // actual stored connection for the client. It is possible for the client to 117 // dial various addresses that all route to the same server. The most common 118 // case for this is the original address the client uses to connect to the 119 // server differs from the advertised address sent by the heartbeat. 120 for i, conn := range conns { 121 if conn.Ctx.Conn.LocalAddr().String() == ctx.Conn.LocalAddr().String() && 122 conn.Ctx.Conn.RemoteAddr().String() == ctx.Conn.RemoteAddr().String() { 123 124 if len(conns) == 1 { 125 // We are deleting the last conn, remove it from the map 126 delete(s.nodeConns, ctx.NodeID) 127 } else { 128 // Slice out the connection we are deleting 129 s.nodeConns[ctx.NodeID] = append(s.nodeConns[ctx.NodeID][:i], s.nodeConns[ctx.NodeID][i+1:]...) 130 } 131 132 return 133 } 134 } 135 } 136 137 // serverWithNodeConn is used to determine which remote server has the most 138 // recent connection to the given node. The local server is not queried. 139 // ErrNoNodeConn is returned if all local peers could be queried but did not 140 // have a connection to the node. Otherwise if a connection could not be found 141 // and there were RPC errors, an error is returned. 142 func (s *Server) serverWithNodeConn(nodeID, region string) (*serverParts, error) { 143 // We skip ourselves. 144 selfAddr := s.LocalMember().Addr.String() 145 146 // Build the request 147 req := &structs.NodeSpecificRequest{ 148 NodeID: nodeID, 149 QueryOptions: structs.QueryOptions{ 150 Region: s.config.Region, 151 }, 152 } 153 154 // Select the list of servers to check based on what region we are querying 155 s.peerLock.RLock() 156 157 var rawTargets []*serverParts 158 if region == s.Region() { 159 rawTargets = make([]*serverParts, 0, len(s.localPeers)) 160 for _, srv := range s.localPeers { 161 rawTargets = append(rawTargets, srv) 162 } 163 } else { 164 peers, ok := s.peers[region] 165 if !ok { 166 s.peerLock.RUnlock() 167 return nil, structs.ErrNoRegionPath 168 } 169 rawTargets = peers 170 } 171 172 targets := make([]*serverParts, 0, len(rawTargets)) 173 for _, target := range rawTargets { 174 targets = append(targets, target.Copy()) 175 } 176 s.peerLock.RUnlock() 177 178 // connections is used to store the servers that have connections to the 179 // requested node. 180 var mostRecentServer *serverParts 181 var mostRecent time.Time 182 183 var rpcErr multierror.Error 184 for _, server := range targets { 185 if server.Addr.String() == selfAddr { 186 continue 187 } 188 189 // Make the RPC 190 var resp structs.NodeConnQueryResponse 191 err := s.connPool.RPC(s.config.Region, server.Addr, server.MajorVersion, 192 "Status.HasNodeConn", &req, &resp) 193 if err != nil { 194 multierror.Append(&rpcErr, fmt.Errorf("failed querying server %q: %v", server.Addr.String(), err)) 195 continue 196 } 197 198 if resp.Connected && resp.Established.After(mostRecent) { 199 mostRecentServer = server 200 mostRecent = resp.Established 201 } 202 } 203 204 // Return an error if there is no route to the node. 205 if mostRecentServer == nil { 206 if err := rpcErr.ErrorOrNil(); err != nil { 207 return nil, err 208 } 209 210 return nil, structs.ErrNoNodeConn 211 } 212 213 return mostRecentServer, nil 214 } 215 216 // NodeRpc is used to make an RPC call to a node. The method takes the 217 // Yamux session for the node and the method to be called. 218 func NodeRpc(session *yamux.Session, method string, args, reply interface{}) error { 219 // Open a new session 220 stream, err := session.Open() 221 if err != nil { 222 return err 223 } 224 defer stream.Close() 225 226 // Write the RpcNomad byte to set the mode 227 if _, err := stream.Write([]byte{byte(pool.RpcNomad)}); err != nil { 228 stream.Close() 229 return err 230 } 231 232 // Make the RPC 233 err = msgpackrpc.CallWithCodec(pool.NewClientCodec(stream), method, args, reply) 234 if err != nil { 235 return err 236 } 237 238 return nil 239 } 240 241 // NodeStreamingRpc is used to make a streaming RPC call to a node. The method 242 // takes the Yamux session for the node and the method to be called. It conducts 243 // the initial handshake and returns a connection to be used or an error. It is 244 // the callers responsibility to close the connection if there is no error. 245 func NodeStreamingRpc(session *yamux.Session, method string) (net.Conn, error) { 246 // Open a new session 247 stream, err := session.Open() 248 if err != nil { 249 return nil, err 250 } 251 252 // Write the RpcNomad byte to set the mode 253 if _, err := stream.Write([]byte{byte(pool.RpcStreaming)}); err != nil { 254 stream.Close() 255 return nil, err 256 } 257 258 // Send the header 259 encoder := codec.NewEncoder(stream, structs.MsgpackHandle) 260 decoder := codec.NewDecoder(stream, structs.MsgpackHandle) 261 header := structs.StreamingRpcHeader{ 262 Method: method, 263 } 264 if err := encoder.Encode(header); err != nil { 265 stream.Close() 266 return nil, err 267 } 268 269 // Wait for the acknowledgement 270 var ack structs.StreamingRpcAck 271 if err := decoder.Decode(&ack); err != nil { 272 stream.Close() 273 return nil, err 274 } 275 276 if ack.Error != "" { 277 stream.Close() 278 return nil, errors.New(ack.Error) 279 } 280 281 return stream, nil 282 } 283 284 // findNodeConnAndForward is a helper for finding the server with a connection 285 // to the given node and forwarding the RPC to the correct server. This does not 286 // work for streaming RPCs. 287 func findNodeConnAndForward(srv *Server, nodeID, method string, args, reply interface{}) error { 288 // Determine the Server that has a connection to the node. 289 srvWithConn, err := srv.serverWithNodeConn(nodeID, srv.Region()) 290 if err != nil { 291 return err 292 } 293 294 if srvWithConn == nil { 295 return structs.ErrNoNodeConn 296 } 297 298 return srv.forwardServer(srvWithConn, method, args, reply) 299 }