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