github.com/aminovpavel/nomad@v0.11.8/nomad/client_rpc.go (about)

     1  package nomad
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"time"
     8  
     9  	"github.com/hashicorp/go-msgpack/codec"
    10  	multierror "github.com/hashicorp/go-multierror"
    11  	msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
    12  	"github.com/hashicorp/nomad/helper/pool"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	"github.com/hashicorp/yamux"
    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.Named("client_rpc").Warn("node exists in node connection map without any connection", "node_id", 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 fmt.Errorf("session open: %v", 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 fmt.Errorf("set mode: %v", 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  }