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  }