Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Displays Consul version of each nodes in UI nodes section #17754

Merged
merged 17 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
931fdfc
update UINodes and UINodeInfo response with consul-version info added…
vijayraghav-io Jun 27, 2023
b3e2ec1
update test cases TestUINodes, TestUINodeInfo
vijayraghav-io Jun 29, 2023
8d0e9a5
added nil check for map
vijayraghav-io Jun 29, 2023
04e5d88
add consul-version in local agent node metadata
vijayraghav-io Jun 28, 2023
28286a2
get consul version from serf member and add this as node meta in cata…
vijayraghav-io Jun 28, 2023
43e50ad
updated ui mock response to include consul versions as node meta
vijayraghav-io Jun 27, 2023
0cf1b70
updated ui trans and added version as query param to node list route
vijayraghav-io Jun 27, 2023
27f34ce
updates in ui templates to display consul version with filter and sorts
vijayraghav-io Jun 27, 2023
2ac76d6
updates in ui - model class, serializers,comparators,predicates for c…
vijayraghav-io Jun 27, 2023
3d618df
added change log for Consul Version Feature
vijayraghav-io Jun 27, 2023
1c757b8
Merge branch 'main' of https://github.com/vijayraghav-io/consul into …
vijayraghav-io Jun 30, 2023
23ce82b
Merge branch 'main' of https://github.com/hashicorp/consul into ui/en…
vijayraghav-io Jul 1, 2023
4dc1c9b
updated to get version from consul service, if for some reason not av…
vijayraghav-io Jul 1, 2023
85a12a9
updated changelog text
vijayraghav-io Jul 7, 2023
25d30a3
updated dependent testcases
vijayraghav-io Jul 7, 2023
7f1d619
multiselection version filter
vijayraghav-io Jul 8, 2023
5174cbf
Update agent/consul/state/catalog.go
vijayraghav-io Jul 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/17754.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
ui: Display the Consul agent version in the nodes list, and allow filtering and sorting of nodes based on versions.
```
1 change: 1 addition & 0 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3999,6 +3999,7 @@ func (a *Agent) loadMetadata(conf *config.RuntimeConfig) error {
meta[k] = v
}
meta[structs.MetaSegmentKey] = conf.SegmentName
meta[structs.MetaConsulVersion] = conf.Version
return a.State.LoadMetadata(meta)
}

Expand Down
3 changes: 2 additions & 1 deletion agent/agent_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1506,7 +1506,8 @@ func TestAgent_Self(t *testing.T) {
require.NoError(t, err)
require.Equal(t, cs[a.config.SegmentName], val.Coord)

delete(val.Meta, structs.MetaSegmentKey) // Added later, not in config.
delete(val.Meta, structs.MetaSegmentKey) // Added later, not in config.
delete(val.Meta, structs.MetaConsulVersion) // Added later, not in config.
require.Equal(t, a.config.NodeMeta, val.Meta)

if tc.expectXDS {
Expand Down
10 changes: 10 additions & 0 deletions agent/consul/leader.go
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,13 @@ AFTER_CHECK:
"partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(),
)

// Get consul version from serf member
// add this as node meta in catalog register request
buildVersion, err := metadata.Build(&member)
if err != nil {
return err
}

// Register with the catalog.
req := structs.RegisterRequest{
Datacenter: s.config.Datacenter,
Expand All @@ -1102,6 +1109,9 @@ AFTER_CHECK:
Output: structs.SerfCheckAliveOutput,
},
EnterpriseMeta: *nodeEntMeta,
NodeMeta: map[string]string{
structs.MetaConsulVersion: buildVersion.String(),
},
}
if node != nil {
req.TaggedAddresses = node.TaggedAddresses
Expand Down
7 changes: 7 additions & 0 deletions agent/consul/state/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -3450,6 +3450,13 @@ func parseNodes(tx ReadTxn, ws memdb.WatchSet, idx uint64,
ws.AddWithLimit(watchLimit, services.WatchCh(), allServicesCh)
for service := services.Next(); service != nil; service = services.Next() {
ns := service.(*structs.ServiceNode).ToNodeService()
// If version isn't defined in node meta, set it from the Consul service meta
if _, ok := dump.Meta[structs.MetaConsulVersion]; !ok && ns.ID == "consul" && ns.Meta["version"] != "" {
if dump.Meta == nil {
dump.Meta = make(map[string]string)
}
dump.Meta[structs.MetaConsulVersion] = ns.Meta["version"]
}
vijayraghav-io marked this conversation as resolved.
Show resolved Hide resolved
dump.Services = append(dump.Services, ns)
}

Expand Down
25 changes: 22 additions & 3 deletions agent/consul/state/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4837,6 +4837,9 @@ func TestStateStore_NodeInfo_NodeDump(t *testing.T) {
}

// Register some nodes
// node1 is registered withOut any nodemeta, and a consul service with id
// 'consul' is added later with meta 'version'. The expected node must have
// meta 'consul-version' with same value
testRegisterNode(t, s, 0, "node1")
testRegisterNode(t, s, 1, "node2")

Expand All @@ -4845,6 +4848,8 @@ func TestStateStore_NodeInfo_NodeDump(t *testing.T) {
testRegisterService(t, s, 3, "node1", "service2")
testRegisterService(t, s, 4, "node2", "service1")
testRegisterService(t, s, 5, "node2", "service2")
// Register consul service with meta 'version' for node1
testRegisterServiceWithMeta(t, s, 10, "node1", "consul", map[string]string{"version": "1.17.0"})

// Register service-level checks
testRegisterCheck(t, s, 6, "node1", "service1", "check1", api.HealthPassing)
Expand Down Expand Up @@ -4894,6 +4899,19 @@ func TestStateStore_NodeInfo_NodeDump(t *testing.T) {
},
},
Services: []*structs.NodeService{
{
ID: "consul",
Service: "consul",
Address: "1.1.1.1",
Meta: map[string]string{"version": "1.17.0"},
Port: 1111,
Weights: &structs.Weights{Passing: 1, Warning: 1},
RaftIndex: structs.RaftIndex{
CreateIndex: 10,
ModifyIndex: 10,
},
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
},
{
ID: "service1",
Service: "service1",
Expand Down Expand Up @@ -4921,6 +4939,7 @@ func TestStateStore_NodeInfo_NodeDump(t *testing.T) {
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
},
},
Meta: map[string]string{"consul-version": "1.17.0"},
},
&structs.NodeInfo{
Node: "node2",
Expand Down Expand Up @@ -4988,7 +5007,7 @@ func TestStateStore_NodeInfo_NodeDump(t *testing.T) {
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 9 {
if idx != 10 {
t.Fatalf("bad index: %d", idx)
}
require.Len(t, dump, 1)
Expand All @@ -4999,8 +5018,8 @@ func TestStateStore_NodeInfo_NodeDump(t *testing.T) {
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 9 {
t.Fatalf("bad index: %d", 9)
if idx != 10 {
t.Fatalf("bad index: %d", idx)
}
if !reflect.DeepEqual(dump, expect) {
t.Fatalf("bad: %#v", dump[0].Services[0])
Expand Down
31 changes: 31 additions & 0 deletions agent/consul/state/state_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,37 @@ func testRegisterServiceWithChangeOpts(t *testing.T, s *Store, idx uint64, nodeI
return svc
}

// testRegisterServiceWithMeta registers service with Meta passed as arg.
func testRegisterServiceWithMeta(t *testing.T, s *Store, idx uint64, nodeID, serviceID string, meta map[string]string, opts ...func(service *structs.NodeService)) *structs.NodeService {
svc := &structs.NodeService{
ID: serviceID,
Service: serviceID,
Address: "1.1.1.1",
Port: 1111,
Meta: meta,
}
for _, o := range opts {
o(svc)
}

if err := s.EnsureService(idx, nodeID, svc); err != nil {
t.Fatalf("err: %s", err)
}

tx := s.db.Txn(false)
defer tx.Abort()
service, err := tx.First(tableServices, indexID, NodeServiceQuery{Node: nodeID, Service: serviceID, PeerName: svc.PeerName})
if err != nil {
t.Fatalf("err: %s", err)
}
if result, ok := service.(*structs.ServiceNode); !ok ||
result.Node != nodeID ||
result.ServiceID != serviceID {
t.Fatalf("bad service: %#v", result)
}
return svc
}

// testRegisterService register a service with given transaction idx
// If the service already exists, transaction number might not be increased
// Use `testRegisterServiceWithChange()` if you want perform a registration that
Expand Down
12 changes: 8 additions & 4 deletions agent/local/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ func TestAgentAntiEntropy_Services(t *testing.T) {
id := services.NodeServices.Node.ID
addrs := services.NodeServices.Node.TaggedAddresses
meta := services.NodeServices.Node.Meta
delete(meta, structs.MetaSegmentKey) // Added later, not in config.
delete(meta, structs.MetaSegmentKey) // Added later, not in config.
delete(meta, structs.MetaConsulVersion) // Added later, not in config.
assert.Equal(t, a.Config.NodeID, id)
assert.Equal(t, a.Config.TaggedAddresses, addrs)
assert.Equal(t, unNilMap(a.Config.NodeMeta), meta)
Expand Down Expand Up @@ -1355,7 +1356,8 @@ func TestAgentAntiEntropy_Checks(t *testing.T) {
id := services.NodeServices.Node.ID
addrs := services.NodeServices.Node.TaggedAddresses
meta := services.NodeServices.Node.Meta
delete(meta, structs.MetaSegmentKey) // Added later, not in config.
delete(meta, structs.MetaSegmentKey) // Added later, not in config.
delete(meta, structs.MetaConsulVersion) // Added later, not in config.
assert.Equal(r, a.Config.NodeID, id)
assert.Equal(r, a.Config.TaggedAddresses, addrs)
assert.Equal(r, unNilMap(a.Config.NodeMeta), meta)
Expand Down Expand Up @@ -2016,7 +2018,8 @@ func TestAgentAntiEntropy_NodeInfo(t *testing.T) {
addrs := services.NodeServices.Node.TaggedAddresses
meta := services.NodeServices.Node.Meta
nodeLocality := services.NodeServices.Node.Locality
delete(meta, structs.MetaSegmentKey) // Added later, not in config.
delete(meta, structs.MetaSegmentKey) // Added later, not in config.
delete(meta, structs.MetaConsulVersion) // Added later, not in config.
require.Equal(t, a.Config.NodeID, id)
require.Equal(t, a.Config.TaggedAddresses, addrs)
require.Equal(t, a.Config.StructLocality(), nodeLocality)
Expand All @@ -2041,7 +2044,8 @@ func TestAgentAntiEntropy_NodeInfo(t *testing.T) {
addrs := services.NodeServices.Node.TaggedAddresses
meta := services.NodeServices.Node.Meta
nodeLocality := services.NodeServices.Node.Locality
delete(meta, structs.MetaSegmentKey) // Added later, not in config.
delete(meta, structs.MetaSegmentKey) // Added later, not in config.
delete(meta, structs.MetaConsulVersion) // Added later, not in config.
require.Equal(t, nodeID, id)
require.Equal(t, a.Config.TaggedAddresses, addrs)
require.Equal(t, a.Config.StructLocality(), nodeLocality)
Expand Down
3 changes: 3 additions & 0 deletions agent/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ const (
// WildcardSpecifier is the string which should be used for specifying a wildcard
// The exact semantics of the wildcard is left up to the code where its used.
WildcardSpecifier = "*"

// MetaConsulVersion is the node metadata key used to store the node's consul version
MetaConsulVersion = "consul-version"
)

var allowedConsulMetaKeysForMeshGateway = map[string]struct{}{MetaWANFederationKey: {}}
Expand Down
96 changes: 96 additions & 0 deletions agent/ui_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import (
"strings"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/serf/serf"

"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/config"
"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/agent/metadata"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/logging"
Expand Down Expand Up @@ -110,32 +113,104 @@ RPC:
return nil, err
}

// Get version info for all serf members into a map of key-address,value-version.
// This logic of calling 'AgentMembersMapAddrVer()' and inserting version info in this func
// can be discarded in future releases ( may be after 3 or 4 minor releases),
// when all the nodes are registered with consul-version in nodemeta.
var err error
mapAddrVer, err := AgentMembersMapAddrVer(s, req)
if err != nil {
return nil, err
}

// Use empty list instead of nil
// Also check if consul-version exists in Meta, else add it
for _, info := range out.Dump {
if info.Services == nil {
info.Services = make([]*structs.NodeService, 0)
}
if info.Checks == nil {
info.Checks = make([]*structs.HealthCheck, 0)
}
// Check if Node Meta - 'consul-version' already exists by virtue of adding
// 'consul-version' during node registration itself.
// If not, get it from mapAddrVer.
if _, ok := info.Meta[structs.MetaConsulVersion]; !ok {
if _, okver := mapAddrVer[info.Address]; okver {
if info.Meta == nil {
info.Meta = make(map[string]string)
}
info.Meta[structs.MetaConsulVersion] = mapAddrVer[info.Address]
}
}
}
if out.Dump == nil {
out.Dump = make(structs.NodeDump, 0)
}

// Use empty list instead of nil
// Also check if consul-version exists in Meta, else add it
for _, info := range out.ImportedDump {
if info.Services == nil {
info.Services = make([]*structs.NodeService, 0)
}
if info.Checks == nil {
info.Checks = make([]*structs.HealthCheck, 0)
}
// Check if Node Meta - 'consul-version' already exists by virtue of adding
// 'consul-version' during node registration itself.
// If not, get it from mapAddrVer.
if _, ok := info.Meta[structs.MetaConsulVersion]; !ok {
if _, okver := mapAddrVer[info.Address]; okver {
if info.Meta == nil {
info.Meta = make(map[string]string)
}
info.Meta[structs.MetaConsulVersion] = mapAddrVer[info.Address]
}
}
}

return append(out.Dump, out.ImportedDump...), nil
}

// AgentMembersMapAddrVer is used to get version info from all serf members into a
// map of key-address,value-version.
func AgentMembersMapAddrVer(s *HTTPHandlers, req *http.Request) (map[string]string, error) {
var members []serf.Member

//Get WAN Members
wanMembers := s.agent.WANMembers()

//Get LAN Members
//Get the request partition and default to that of the agent.
entMeta := s.agent.AgentEnterpriseMeta()
if err := s.parseEntMetaPartition(req, entMeta); err != nil {
return nil, err
}
filter := consul.LANMemberFilter{
Partition: entMeta.PartitionOrDefault(),
}
filter.AllSegments = true
lanMembers, err := s.agent.delegate.LANMembers(filter)
if err != nil {
return nil, err
}

//aggregate members
members = append(wanMembers, lanMembers...)

//create a map with key as IPv4 address and value as consul-version
mapAddrVer := make(map[string]string, len(members))
for i := range members {
buildVersion, err := metadata.Build(&members[i])
if err == nil {
mapAddrVer[members[i].Addr.String()] = buildVersion.String()
}
}

return mapAddrVer, nil
}

// UINodeInfo is used to get info on a single node in a given datacenter. We return a
// NodeInfo which provides overview information for the node
func (s *HTTPHandlers) UINodeInfo(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
Expand Down Expand Up @@ -172,6 +247,16 @@ RPC:
return nil, err
}

// Get version info for all serf members into a map of key-address,value-version.
// This logic of calling 'AgentMembersMapAddrVer()' and inserting version info in this func
// can be discarded in future releases ( may be after 3 or 4 minor releases),
// when all the nodes are registered with consul-version in nodemeta.
var err error
mapAddrVer, err := AgentMembersMapAddrVer(s, req)
if err != nil {
return nil, err
}

// Return only the first entry
if len(out.Dump) > 0 {
info := out.Dump[0]
Expand All @@ -181,6 +266,17 @@ RPC:
if info.Checks == nil {
info.Checks = make([]*structs.HealthCheck, 0)
}
// Check if Node Meta - 'consul-version' already exists by virtue of adding
// 'consul-version' during node registration itself.
// If not, get it from mapAddrVer.
if _, ok := info.Meta[structs.MetaConsulVersion]; !ok {
if _, okver := mapAddrVer[info.Address]; okver {
if info.Meta == nil {
info.Meta = make(map[string]string)
}
info.Meta[structs.MetaConsulVersion] = mapAddrVer[info.Address]
}
}
return info, nil
}

Expand Down
6 changes: 6 additions & 0 deletions agent/ui_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ func TestUINodes(t *testing.T) {
require.Len(t, nodes[2].Services, 0)
require.NotNil(t, nodes[1].Checks)
require.Len(t, nodes[2].Services, 0)

// check for consul-version in node meta
require.Equal(t, nodes[0].Meta[structs.MetaConsulVersion], a.Config.Version)
}

func TestUINodes_Filter(t *testing.T) {
Expand Down Expand Up @@ -260,6 +263,9 @@ func TestUINodeInfo(t *testing.T) {
node.Checks == nil || len(node.Checks) != 0 {
t.Fatalf("bad: %v", node)
}

// check for consul-version in node meta
require.Equal(t, node.Meta[structs.MetaConsulVersion], a.Config.Version)
}

func TestUIServices(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions api/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func TestAPI_CatalogNodes(t *testing.T) {
},
Meta: map[string]string{
"consul-network-segment": "",
"consul-version": s.Config.Version,
},
}
require.Equal(r, want, got)
Expand Down
Loading