2015-09-08 18:41:03 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-09-08 21:26:53 +00:00
|
|
|
"net/url"
|
2015-09-08 18:41:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Agent encapsulates an API client which talks to Nomad's
|
|
|
|
// agent endpoints for a specific node.
|
|
|
|
type Agent struct {
|
|
|
|
client *Client
|
2015-09-08 18:51:20 +00:00
|
|
|
|
|
|
|
// Cache static agent info
|
2015-09-08 19:01:54 +00:00
|
|
|
nodeName string
|
|
|
|
datacenter string
|
2015-09-08 19:13:39 +00:00
|
|
|
region string
|
2015-09-08 18:41:03 +00:00
|
|
|
}
|
|
|
|
|
2016-10-17 17:48:04 +00:00
|
|
|
// KeyringResponse is a unified key response and can be used for install,
|
|
|
|
// remove, use, as well as listing key queries.
|
|
|
|
type KeyringResponse struct {
|
|
|
|
Messages map[string]string
|
|
|
|
Keys map[string]int
|
|
|
|
NumNodes int
|
|
|
|
}
|
|
|
|
|
|
|
|
// KeyringRequest is request objects for serf key operations.
|
|
|
|
type KeyringRequest struct {
|
|
|
|
Key string
|
|
|
|
}
|
|
|
|
|
2015-09-08 18:41:03 +00:00
|
|
|
// Agent returns a new agent which can be used to query
|
|
|
|
// the agent-specific endpoints.
|
|
|
|
func (c *Client) Agent() *Agent {
|
|
|
|
return &Agent{client: c}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Self is used to query the /v1/agent/self endpoint and
|
|
|
|
// returns information specific to the running agent.
|
|
|
|
func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
|
|
|
var out map[string]map[string]interface{}
|
|
|
|
|
|
|
|
// Query the self endpoint on the agent
|
|
|
|
_, err := a.client.query("/v1/agent/self", &out, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed querying self endpoint: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-09-08 19:13:39 +00:00
|
|
|
// Populate the cache for faster queries
|
|
|
|
a.populateCache(out)
|
|
|
|
|
2015-09-08 18:41:03 +00:00
|
|
|
return out, nil
|
|
|
|
}
|
2015-09-08 18:51:20 +00:00
|
|
|
|
2015-09-08 19:13:39 +00:00
|
|
|
// populateCache is used to insert various pieces of static
|
|
|
|
// data into the agent handle. This is used during subsequent
|
|
|
|
// lookups for the same data later on to save the round trip.
|
|
|
|
func (a *Agent) populateCache(info map[string]map[string]interface{}) {
|
|
|
|
if a.nodeName == "" {
|
|
|
|
a.nodeName, _ = info["member"]["Name"].(string)
|
|
|
|
}
|
|
|
|
if tags, ok := info["member"]["Tags"].(map[string]interface{}); ok {
|
|
|
|
if a.datacenter == "" {
|
|
|
|
a.datacenter, _ = tags["dc"].(string)
|
|
|
|
}
|
|
|
|
if a.region == "" {
|
|
|
|
a.region, _ = tags["region"].(string)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-08 18:51:20 +00:00
|
|
|
// NodeName is used to query the Nomad agent for its node name.
|
|
|
|
func (a *Agent) NodeName() (string, error) {
|
|
|
|
// Return from cache if we have it
|
|
|
|
if a.nodeName != "" {
|
|
|
|
return a.nodeName, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query the node name
|
2015-09-08 19:13:39 +00:00
|
|
|
_, err := a.Self()
|
|
|
|
return a.nodeName, err
|
2015-09-08 18:51:20 +00:00
|
|
|
}
|
2015-09-08 19:01:54 +00:00
|
|
|
|
|
|
|
// Datacenter is used to return the name of the datacenter which
|
|
|
|
// the agent is a member of.
|
|
|
|
func (a *Agent) Datacenter() (string, error) {
|
|
|
|
// Return from cache if we have it
|
|
|
|
if a.datacenter != "" {
|
|
|
|
return a.datacenter, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query the agent for the DC
|
2015-09-08 19:13:39 +00:00
|
|
|
_, err := a.Self()
|
|
|
|
return a.datacenter, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Region is used to look up the region the agent is in.
|
|
|
|
func (a *Agent) Region() (string, error) {
|
|
|
|
// Return from cache if we have it
|
|
|
|
if a.region != "" {
|
|
|
|
return a.region, nil
|
2015-09-08 19:01:54 +00:00
|
|
|
}
|
2015-09-08 19:13:39 +00:00
|
|
|
|
|
|
|
// Query the agent for the region
|
|
|
|
_, err := a.Self()
|
|
|
|
return a.region, err
|
2015-09-08 19:01:54 +00:00
|
|
|
}
|
2015-09-08 21:26:53 +00:00
|
|
|
|
|
|
|
// Join is used to instruct a server node to join another server
|
|
|
|
// via the gossip protocol. Multiple addresses may be specified.
|
2015-09-11 22:40:54 +00:00
|
|
|
// We attempt to join all of the hosts in the list. Returns the
|
|
|
|
// number of nodes successfully joined and any error. If one or
|
2015-09-08 21:26:53 +00:00
|
|
|
// more nodes have a successful result, no error is returned.
|
2015-09-11 22:40:54 +00:00
|
|
|
func (a *Agent) Join(addrs ...string) (int, error) {
|
2015-09-08 21:26:53 +00:00
|
|
|
// Accumulate the addresses
|
|
|
|
v := url.Values{}
|
|
|
|
for _, addr := range addrs {
|
|
|
|
v.Add("address", addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the join request
|
|
|
|
var resp joinResponse
|
|
|
|
_, err := a.client.write("/v1/agent/join?"+v.Encode(), nil, &resp, nil)
|
|
|
|
if err != nil {
|
2015-09-11 22:40:54 +00:00
|
|
|
return 0, fmt.Errorf("failed joining: %s", err)
|
2015-09-08 21:26:53 +00:00
|
|
|
}
|
|
|
|
if resp.Error != "" {
|
2015-09-11 22:40:54 +00:00
|
|
|
return 0, fmt.Errorf("failed joining: %s", resp.Error)
|
2015-09-08 21:26:53 +00:00
|
|
|
}
|
2015-09-14 21:04:30 +00:00
|
|
|
return resp.NumJoined, nil
|
2015-09-08 21:26:53 +00:00
|
|
|
}
|
|
|
|
|
2015-09-08 21:47:29 +00:00
|
|
|
// Members is used to query all of the known server members
|
2016-11-06 18:29:09 +00:00
|
|
|
func (a *Agent) Members() (*ServerMembers, error) {
|
|
|
|
var resp *ServerMembers
|
2015-09-08 21:47:29 +00:00
|
|
|
|
|
|
|
// Query the known members
|
|
|
|
_, err := a.client.query("/v1/agent/members", &resp, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForceLeave is used to eject an existing node from the cluster.
|
|
|
|
func (a *Agent) ForceLeave(node string) error {
|
|
|
|
_, err := a.client.write("/v1/agent/force-leave?node="+node, nil, nil, nil)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-25 04:17:33 +00:00
|
|
|
// Servers is used to query the list of servers on a client node.
|
|
|
|
func (a *Agent) Servers() ([]string, error) {
|
|
|
|
var resp []string
|
|
|
|
_, err := a.client.query("/v1/agent/servers", &resp, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetServers is used to update the list of servers on a client node.
|
|
|
|
func (a *Agent) SetServers(addrs []string) error {
|
|
|
|
// Accumulate the addresses
|
|
|
|
v := url.Values{}
|
|
|
|
for _, addr := range addrs {
|
|
|
|
v.Add("address", addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := a.client.write("/v1/agent/servers?"+v.Encode(), nil, nil, nil)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-10-17 17:48:04 +00:00
|
|
|
// ListKeys returns the list of installed keys
|
|
|
|
func (a *Agent) ListKeys() (*KeyringResponse, error) {
|
|
|
|
var resp KeyringResponse
|
|
|
|
_, err := a.client.query("/v1/agent/keyring/list", &resp, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// InstallKey installs a key in the keyrings of all the serf members
|
|
|
|
func (a *Agent) InstallKey(key string) (*KeyringResponse, error) {
|
|
|
|
args := KeyringRequest{
|
|
|
|
Key: key,
|
|
|
|
}
|
|
|
|
var resp KeyringResponse
|
|
|
|
_, err := a.client.write("/v1/agent/keyring/install", &args, &resp, nil)
|
|
|
|
return &resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// UseKey uses a key from the keyring of serf members
|
|
|
|
func (a *Agent) UseKey(key string) (*KeyringResponse, error) {
|
|
|
|
args := KeyringRequest{
|
|
|
|
Key: key,
|
|
|
|
}
|
|
|
|
var resp KeyringResponse
|
|
|
|
_, err := a.client.write("/v1/agent/keyring/use", &args, &resp, nil)
|
|
|
|
return &resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveKey removes a particular key from keyrings of serf members
|
|
|
|
func (a *Agent) RemoveKey(key string) (*KeyringResponse, error) {
|
|
|
|
args := KeyringRequest{
|
|
|
|
Key: key,
|
|
|
|
}
|
|
|
|
var resp KeyringResponse
|
|
|
|
_, err := a.client.write("/v1/agent/keyring/remove", &args, &resp, nil)
|
|
|
|
return &resp, err
|
|
|
|
}
|
|
|
|
|
2015-09-08 21:26:53 +00:00
|
|
|
// joinResponse is used to decode the response we get while
|
|
|
|
// sending a member join request.
|
|
|
|
type joinResponse struct {
|
2015-09-14 21:04:30 +00:00
|
|
|
NumJoined int `json:"num_joined"`
|
|
|
|
Error string `json:"error"`
|
2015-09-08 21:26:53 +00:00
|
|
|
}
|
2015-09-08 21:47:29 +00:00
|
|
|
|
2016-11-06 18:29:09 +00:00
|
|
|
type ServerMembers struct {
|
|
|
|
ServerName string
|
|
|
|
Region string
|
|
|
|
DC string
|
|
|
|
Members []*AgentMember
|
|
|
|
}
|
|
|
|
|
2015-09-08 21:47:29 +00:00
|
|
|
// AgentMember represents a cluster member known to the agent
|
|
|
|
type AgentMember struct {
|
|
|
|
Name string
|
|
|
|
Addr string
|
|
|
|
Port uint16
|
|
|
|
Tags map[string]string
|
2015-09-11 22:19:43 +00:00
|
|
|
Status string
|
2015-09-08 21:47:29 +00:00
|
|
|
ProtocolMin uint8
|
|
|
|
ProtocolMax uint8
|
|
|
|
ProtocolCur uint8
|
|
|
|
DelegateMin uint8
|
|
|
|
DelegateMax uint8
|
|
|
|
DelegateCur uint8
|
|
|
|
}
|
2015-10-21 12:35:34 +00:00
|
|
|
|
|
|
|
// AgentMembersNameSort implements sort.Interface for []*AgentMembersNameSort
|
|
|
|
// based on the Name, DC and Region
|
|
|
|
type AgentMembersNameSort []*AgentMember
|
|
|
|
|
|
|
|
func (a AgentMembersNameSort) Len() int { return len(a) }
|
|
|
|
func (a AgentMembersNameSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
func (a AgentMembersNameSort) Less(i, j int) bool {
|
|
|
|
if a[i].Tags["region"] != a[j].Tags["region"] {
|
|
|
|
return a[i].Tags["region"] < a[j].Tags["region"]
|
|
|
|
}
|
|
|
|
|
|
|
|
if a[i].Tags["dc"] != a[j].Tags["dc"] {
|
|
|
|
return a[i].Tags["dc"] < a[j].Tags["dc"]
|
|
|
|
}
|
|
|
|
|
|
|
|
return a[i].Name < a[j].Name
|
|
|
|
|
|
|
|
}
|