github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/api/agent.go (about)

     1  package api
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/url"
     7  )
     8  
     9  // Agent encapsulates an API client which talks to Nomad's
    10  // agent endpoints for a specific node.
    11  type Agent struct {
    12  	client *Client
    13  
    14  	// Cache static agent info
    15  	nodeName   string
    16  	datacenter string
    17  	region     string
    18  }
    19  
    20  // KeyringResponse is a unified key response and can be used for install,
    21  // remove, use, as well as listing key queries.
    22  type KeyringResponse struct {
    23  	Messages map[string]string
    24  	Keys     map[string]int
    25  	NumNodes int
    26  }
    27  
    28  // KeyringRequest is request objects for serf key operations.
    29  type KeyringRequest struct {
    30  	Key string
    31  }
    32  
    33  // Agent returns a new agent which can be used to query
    34  // the agent-specific endpoints.
    35  func (c *Client) Agent() *Agent {
    36  	return &Agent{client: c}
    37  }
    38  
    39  // Self is used to query the /v1/agent/self endpoint and
    40  // returns information specific to the running agent.
    41  func (a *Agent) Self() (*AgentSelf, error) {
    42  	var out *AgentSelf
    43  
    44  	// Query the self endpoint on the agent
    45  	_, err := a.client.query("/v1/agent/self", &out, nil)
    46  	if err != nil {
    47  		return nil, fmt.Errorf("failed querying self endpoint: %s", err)
    48  	}
    49  
    50  	// Populate the cache for faster queries
    51  	a.populateCache(out)
    52  
    53  	return out, nil
    54  }
    55  
    56  // populateCache is used to insert various pieces of static
    57  // data into the agent handle. This is used during subsequent
    58  // lookups for the same data later on to save the round trip.
    59  func (a *Agent) populateCache(self *AgentSelf) {
    60  	if a.nodeName == "" {
    61  		a.nodeName = self.Member.Name
    62  	}
    63  	if a.datacenter == "" {
    64  		if val, ok := self.Config["Datacenter"]; ok {
    65  			a.datacenter, _ = val.(string)
    66  		}
    67  	}
    68  	if a.region == "" {
    69  		if val, ok := self.Config["Region"]; ok {
    70  			a.region, _ = val.(string)
    71  		}
    72  	}
    73  }
    74  
    75  // NodeName is used to query the Nomad agent for its node name.
    76  func (a *Agent) NodeName() (string, error) {
    77  	// Return from cache if we have it
    78  	if a.nodeName != "" {
    79  		return a.nodeName, nil
    80  	}
    81  
    82  	// Query the node name
    83  	_, err := a.Self()
    84  	return a.nodeName, err
    85  }
    86  
    87  // Datacenter is used to return the name of the datacenter which
    88  // the agent is a member of.
    89  func (a *Agent) Datacenter() (string, error) {
    90  	// Return from cache if we have it
    91  	if a.datacenter != "" {
    92  		return a.datacenter, nil
    93  	}
    94  
    95  	// Query the agent for the DC
    96  	_, err := a.Self()
    97  	return a.datacenter, err
    98  }
    99  
   100  // Region is used to look up the region the agent is in.
   101  func (a *Agent) Region() (string, error) {
   102  	// Return from cache if we have it
   103  	if a.region != "" {
   104  		return a.region, nil
   105  	}
   106  
   107  	// Query the agent for the region
   108  	_, err := a.Self()
   109  	return a.region, err
   110  }
   111  
   112  // Join is used to instruct a server node to join another server
   113  // via the gossip protocol. Multiple addresses may be specified.
   114  // We attempt to join all of the hosts in the list. Returns the
   115  // number of nodes successfully joined and any error. If one or
   116  // more nodes have a successful result, no error is returned.
   117  func (a *Agent) Join(addrs ...string) (int, error) {
   118  	// Accumulate the addresses
   119  	v := url.Values{}
   120  	for _, addr := range addrs {
   121  		v.Add("address", addr)
   122  	}
   123  
   124  	// Send the join request
   125  	var resp joinResponse
   126  	_, err := a.client.write("/v1/agent/join?"+v.Encode(), nil, &resp, nil)
   127  	if err != nil {
   128  		return 0, fmt.Errorf("failed joining: %s", err)
   129  	}
   130  	if resp.Error != "" {
   131  		return 0, fmt.Errorf("failed joining: %s", resp.Error)
   132  	}
   133  	return resp.NumJoined, nil
   134  }
   135  
   136  // Members is used to query all of the known server members
   137  func (a *Agent) Members() (*ServerMembers, error) {
   138  	var resp *ServerMembers
   139  
   140  	// Query the known members
   141  	_, err := a.client.query("/v1/agent/members", &resp, nil)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	return resp, nil
   146  }
   147  
   148  // ForceLeave is used to eject an existing node from the cluster.
   149  func (a *Agent) ForceLeave(node string) error {
   150  	_, err := a.client.write("/v1/agent/force-leave?node="+node, nil, nil, nil)
   151  	return err
   152  }
   153  
   154  // Servers is used to query the list of servers on a client node.
   155  func (a *Agent) Servers() ([]string, error) {
   156  	var resp []string
   157  	_, err := a.client.query("/v1/agent/servers", &resp, nil)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	return resp, nil
   162  }
   163  
   164  // SetServers is used to update the list of servers on a client node.
   165  func (a *Agent) SetServers(addrs []string) error {
   166  	// Accumulate the addresses
   167  	v := url.Values{}
   168  	for _, addr := range addrs {
   169  		v.Add("address", addr)
   170  	}
   171  
   172  	_, err := a.client.write("/v1/agent/servers?"+v.Encode(), nil, nil, nil)
   173  	return err
   174  }
   175  
   176  // ListKeys returns the list of installed keys
   177  func (a *Agent) ListKeys() (*KeyringResponse, error) {
   178  	var resp KeyringResponse
   179  	_, err := a.client.query("/v1/agent/keyring/list", &resp, nil)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	return &resp, nil
   184  }
   185  
   186  // InstallKey installs a key in the keyrings of all the serf members
   187  func (a *Agent) InstallKey(key string) (*KeyringResponse, error) {
   188  	args := KeyringRequest{
   189  		Key: key,
   190  	}
   191  	var resp KeyringResponse
   192  	_, err := a.client.write("/v1/agent/keyring/install", &args, &resp, nil)
   193  	return &resp, err
   194  }
   195  
   196  // UseKey uses a key from the keyring of serf members
   197  func (a *Agent) UseKey(key string) (*KeyringResponse, error) {
   198  	args := KeyringRequest{
   199  		Key: key,
   200  	}
   201  	var resp KeyringResponse
   202  	_, err := a.client.write("/v1/agent/keyring/use", &args, &resp, nil)
   203  	return &resp, err
   204  }
   205  
   206  // RemoveKey removes a particular key from keyrings of serf members
   207  func (a *Agent) RemoveKey(key string) (*KeyringResponse, error) {
   208  	args := KeyringRequest{
   209  		Key: key,
   210  	}
   211  	var resp KeyringResponse
   212  	_, err := a.client.write("/v1/agent/keyring/remove", &args, &resp, nil)
   213  	return &resp, err
   214  }
   215  
   216  // Health queries the agent's health
   217  func (a *Agent) Health() (*AgentHealthResponse, error) {
   218  	req, err := a.client.newRequest("GET", "/v1/agent/health")
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	var health AgentHealthResponse
   224  	_, resp, err := a.client.doRequest(req)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	defer resp.Body.Close()
   229  
   230  	// Always try to decode the response as JSON
   231  	err = json.NewDecoder(resp.Body).Decode(&health)
   232  	if err == nil {
   233  		return &health, nil
   234  	}
   235  
   236  	// Return custom error when response is not expected JSON format
   237  	return nil, fmt.Errorf("unable to unmarhsal response with status %d: %v", resp.StatusCode, err)
   238  }
   239  
   240  // joinResponse is used to decode the response we get while
   241  // sending a member join request.
   242  type joinResponse struct {
   243  	NumJoined int    `json:"num_joined"`
   244  	Error     string `json:"error"`
   245  }
   246  
   247  type ServerMembers struct {
   248  	ServerName   string
   249  	ServerRegion string
   250  	ServerDC     string
   251  	Members      []*AgentMember
   252  }
   253  
   254  type AgentSelf struct {
   255  	Config map[string]interface{}       `json:"config"`
   256  	Member AgentMember                  `json:"member"`
   257  	Stats  map[string]map[string]string `json:"stats"`
   258  }
   259  
   260  // AgentMember represents a cluster member known to the agent
   261  type AgentMember struct {
   262  	Name        string
   263  	Addr        string
   264  	Port        uint16
   265  	Tags        map[string]string
   266  	Status      string
   267  	ProtocolMin uint8
   268  	ProtocolMax uint8
   269  	ProtocolCur uint8
   270  	DelegateMin uint8
   271  	DelegateMax uint8
   272  	DelegateCur uint8
   273  }
   274  
   275  // AgentMembersNameSort implements sort.Interface for []*AgentMembersNameSort
   276  // based on the Name, DC and Region
   277  type AgentMembersNameSort []*AgentMember
   278  
   279  func (a AgentMembersNameSort) Len() int      { return len(a) }
   280  func (a AgentMembersNameSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   281  func (a AgentMembersNameSort) Less(i, j int) bool {
   282  	if a[i].Tags["region"] != a[j].Tags["region"] {
   283  		return a[i].Tags["region"] < a[j].Tags["region"]
   284  	}
   285  
   286  	if a[i].Tags["dc"] != a[j].Tags["dc"] {
   287  		return a[i].Tags["dc"] < a[j].Tags["dc"]
   288  	}
   289  
   290  	return a[i].Name < a[j].Name
   291  
   292  }
   293  
   294  // AgentHealthResponse is the response from the Health endpoint desecribing an
   295  // agent's health.
   296  type AgentHealthResponse struct {
   297  	Client *AgentHealth `json:"client,omitempty"`
   298  	Server *AgentHealth `json:"server,omitempty"`
   299  }
   300  
   301  // AgentHealth describes the Client or Server's health in a Health request.
   302  type AgentHealth struct {
   303  	// Ok is false if the agent is unhealthy
   304  	Ok bool `json:"ok"`
   305  
   306  	// Message describes why the agent is unhealthy
   307  	Message string `json:"message"`
   308  }