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