From f5121d261e9f53a9626f8a9fd344b5ff54328d48 Mon Sep 17 00:00:00 2001 From: Shishir Date: Fri, 15 Apr 2022 17:22:45 -0700 Subject: [PATCH] Add os to NodeListStub struct. (#12497) * Add os to NodeListStub struct. Signed-off-by: Shishir Mahajan * Add os as a query param to /v1/nodes. Signed-off-by: Shishir Mahajan * Add test: os as a query param to /v1/nodes. Signed-off-by: Shishir Mahajan --- api/nodes.go | 1 + command/agent/node_endpoint.go | 16 ++++++-- command/agent/node_endpoint_test.go | 64 +++++++++++++++++++++++++++++ command/node_status.go | 19 ++++----- nomad/structs/structs.go | 9 ++++ website/content/api-docs/nodes.mdx | 11 +++++ 6 files changed, 105 insertions(+), 15 deletions(-) diff --git a/api/nodes.go b/api/nodes.go index 66b25a592..e91a3894f 100644 --- a/api/nodes.go +++ b/api/nodes.go @@ -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 diff --git a/command/agent/node_endpoint.go b/command/agent/node_endpoint.go index 90175f4fb..046227dbb 100644 --- a/command/agent/node_endpoint.go +++ b/command/agent/node_endpoint.go @@ -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 } diff --git a/command/agent/node_endpoint_test.go b/command/agent/node_endpoint_test.go index 0b682b3ad..406879a93 100644 --- a/command/agent/node_endpoint_test.go +++ b/command/agent/node_endpoint_test.go @@ -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) { diff --git a/command/node_status.go b/command/node_status.go index 546d64ba8..4dabf6358 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -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", diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index bc1d9627d..0dadc9375 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -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 diff --git a/website/content/api-docs/nodes.mdx b/website/content/api-docs/nodes.mdx index 7c234b623..c08739e81 100644 --- a/website/content/api-docs/nodes.mdx +++ b/website/content/api-docs/nodes.mdx @@ -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,