Add os to NodeListStub struct. (#12497)

* Add os to NodeListStub struct.

Signed-off-by: Shishir Mahajan <smahajan@roblox.com>

* Add os as a query param to /v1/nodes.

Signed-off-by: Shishir Mahajan <smahajan@roblox.com>

* Add test: os as a query param to /v1/nodes.

Signed-off-by: Shishir Mahajan <smahajan@roblox.com>
This commit is contained in:
Shishir 2022-04-15 17:22:45 -07:00 committed by GitHub
parent 826d9d47f9
commit f5121d261e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 15 deletions

View File

@ -906,6 +906,7 @@ func (v *StatValue) String() string {
type NodeListStub struct {
Address string
ID string
Attributes map[string]string `json:",omitempty"`
Datacenter string
Name string
NodeClass string

View File

@ -18,15 +18,24 @@ func (s *HTTPServer) NodesRequest(resp http.ResponseWriter, req *http.Request) (
return nil, nil
}
args.Fields = &structs.NodeStubFields{}
// Parse resources field selection
resources, err := parseBool(req, "resources")
if err != nil {
return nil, err
}
if resources != nil {
args.Fields = &structs.NodeStubFields{
Resources: *resources,
}
args.Fields.Resources = *resources
}
// Parse OS
os, err := parseBool(req, "os")
if err != nil {
return nil, err
}
if os != nil {
args.Fields.OS = *os
}
var out structs.NodeListResponse
@ -38,6 +47,7 @@ func (s *HTTPServer) NodesRequest(resp http.ResponseWriter, req *http.Request) (
if out.Nodes == nil {
out.Nodes = make([]*structs.NodeListStub, 0)
}
return out.Nodes, nil
}

View File

@ -119,6 +119,70 @@ func TestHTTP_NodesPrefixList(t *testing.T) {
})
}
func TestHTTP_NodesOSList(t *testing.T) {
ci.Parallel(t)
httpTest(t, nil, func(s *TestAgent) {
ids := []string{
"12345670-abcd-efab-cdef-123456789abc",
"12345671-aaaa-efab-cdef-123456789abc",
}
oss := []string{
"ubuntu",
"centos",
}
for i := 0; i < 2; i++ {
// Create the node
node := mock.Node()
node.ID = ids[i]
node.Attributes["os.name"] = oss[i]
args := structs.NodeRegisterRequest{
Node: node,
WriteRequest: structs.WriteRequest{Region: "global"},
}
var resp structs.NodeUpdateResponse
if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil {
t.Fatalf("err: %v", err)
}
}
// Make the HTTP request
req, err := http.NewRequest("GET", "/v1/nodes?prefix=123456&os=true", nil)
if err != nil {
t.Fatalf("err: %v", err)
}
respW := httptest.NewRecorder()
// Make the request
obj, err := s.Server.NodesRequest(respW, req)
if err != nil {
t.Fatalf("err: %v", err)
}
// Check for the index
if respW.Header().Get("X-Nomad-Index") == "" {
t.Fatalf("missing index")
}
if respW.Header().Get("X-Nomad-KnownLeader") != "true" {
t.Fatalf("missing known leader")
}
if respW.Header().Get("X-Nomad-LastContact") == "" {
t.Fatalf("missing last contact")
}
// Check the nodes attributes
nodes := obj.([]*structs.NodeListStub)
if len(nodes) != 2 {
t.Fatalf("bad: %#v", nodes)
}
for index, node := range nodes {
if node.Attributes["os.name"] != oss[index] {
t.Fatalf("Expected: %s, Got: %s", oss[index], node.Attributes["os.name"])
}
}
})
}
func TestHTTP_NodeForceEval(t *testing.T) {
ci.Parallel(t)
httpTest(t, nil, func(s *TestAgent) {

View File

@ -173,8 +173,13 @@ func (c *NodeStatusCommand) Run(args []string) int {
return 1
}
var q *api.QueryOptions
if c.os {
q = &api.QueryOptions{Params: map[string]string{"os": "true"}}
}
// Query the node info
nodes, _, err := client.Nodes().List(nil)
nodes, _, err := client.Nodes().List(q)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error querying node status: %s", err))
return 1
@ -231,24 +236,14 @@ func (c *NodeStatusCommand) Run(args []string) int {
out[0] += "|Running Allocs"
}
queryOptions := &api.QueryOptions{AllowStale: true}
var nodeInfo *api.Node
for i, node := range nodes {
if c.os {
nodeInfo, _, err = client.Nodes().Info(node.ID, queryOptions)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting node info: %s", err))
return 1
}
}
out[i+1] = fmt.Sprintf("%s|%s|%s|%s",
limit(node.ID, c.length),
node.Datacenter,
node.Name,
node.NodeClass)
if c.os {
out[i+1] += fmt.Sprintf("|%s", nodeInfo.Attributes["os.name"])
out[i+1] += fmt.Sprintf("|%s", node.Attributes["os.name"])
}
if c.verbose {
out[i+1] += fmt.Sprintf("|%s|%s",

View File

@ -2215,6 +2215,13 @@ func (n *Node) Stub(fields *NodeStubFields) *NodeListStub {
s.NodeResources = n.NodeResources
s.ReservedResources = n.ReservedResources
}
// Fetch key attributes from the main Attributes map.
if fields.OS {
m := make(map[string]string)
m["os.name"] = n.Attributes["os.name"]
s.Attributes = m
}
}
return s
@ -2225,6 +2232,7 @@ func (n *Node) Stub(fields *NodeStubFields) *NodeListStub {
type NodeListStub struct {
Address string
ID string
Attributes map[string]string `json:",omitempty"`
Datacenter string
Name string
NodeClass string
@ -2245,6 +2253,7 @@ type NodeListStub struct {
// NodeStubFields defines which fields are included in the NodeListStub.
type NodeStubFields struct {
Resources bool
OS bool
}
// Resources is used to define the resources available

View File

@ -34,6 +34,9 @@ The table below shows this endpoint's support for
- `resources` `(bool: false)` - Specifies whether or not to include the
`NodeResources` and `ReservedResources` fields in the response.
- `os` `(bool: false)` - Specifies whether or not to include special attributes
such as operating system name in the response.
### Sample Request
```shell-session
@ -46,12 +49,20 @@ $ curl \
http://localhost:4646/v1/nodes?prefix=f7476465
```
```shell-session
$ curl \
http://localhost:4646/v1/nodes?os=true
```
### Sample Response
```json
[
{
"Address": "10.138.0.5",
"Attributes": {
"os.name": "ubuntu"
},
"CreateIndex": 6,
"Datacenter": "dc1",
"Drain": false,