Skip to content

Commit

Permalink
Add -D option to get departed members. Fix get persistence error (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmiddlet2666 committed Mar 26, 2024
1 parent b1294eb commit e747b6d
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 32 deletions.
19 changes: 18 additions & 1 deletion docs/reference/15_members.adoc
@@ -1,6 +1,6 @@
///////////////////////////////////////////////////////////////////////////////

Copyright (c) 2021, 2023 Oracle and/or its affiliates.
Copyright (c) 2021, 2024 Oracle and/or its affiliates.
Licensed under the Universal Permissive License v 1.0 as shown at
https://oss.oracle.com/licenses/upl.

Expand Down Expand Up @@ -68,6 +68,23 @@ NODE ID ADDRESS PORT PROCESS MEMBER ROLE STORAGE MAX
NOTE: You can also use `-o wide` to display more columns.
Display all departed members.
[source,bash]
----
cohctl get members -c local -D
----
Output:
[source,bash]
----
NODE ID TIMESTAMP ADDRESS MACHINE ID LOCATION ROLE
3 2024-03-26 09:28:12.65 127.0.0.1:49170 10131 machine:localhost,process:5892,member:storage-2 CoherenceServer
5 2024-03-26 09:26:22.24 127.0.0.1:50251 10131 machine:localhost,process:6600,member:storage-3 CoherenceServer
4 2024-03-26 08:11:00.537 127.0.0.1:50250 10131 machine:localhost,process:6601,member:storage-4 CoherenceServer
----
NOTE: Members are displayed in descending order of departure time.
[#get-network-stats]
==== Get Network Stats
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/cluster.go
Expand Up @@ -558,7 +558,7 @@ addition information as well as '-v' to displayed additional information.`,

sb.WriteString("\nMEMBERS\n")
sb.WriteString("-------\n")
sb.WriteString(FormatMembers(members.Members, verboseOutput, storageMap, false))
sb.WriteString(FormatMembers(members.Members, verboseOutput, storageMap, false, cluster.MembersDepartureCount))

sb.WriteString("\nSERVICES\n")
sb.WriteString("--------\n")
Expand Down
34 changes: 27 additions & 7 deletions pkg/cmd/formatting.go
Expand Up @@ -90,12 +90,12 @@ func FormatCurrentCluster(clusterName string) string {
// FormatCluster returns a string representing a cluster.
func FormatCluster(cluster config.Cluster) string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("Cluster Name: %s\n", cluster.ClusterName))
sb.WriteString(fmt.Sprintf("Version: %s\n", cluster.Version))
sb.WriteString(fmt.Sprintf("Cluster TotalSize: %d\n", cluster.ClusterSize))
sb.WriteString(fmt.Sprintf("License Mode: %s\n", cluster.LicenseMode))
sb.WriteString(fmt.Sprintf("Departure Count: %d\n", cluster.MembersDepartureCount))
sb.WriteString(fmt.Sprintf("Running: %v\n", cluster.Running))
sb.WriteString(fmt.Sprintf("Cluster Name: %s\n", cluster.ClusterName))
sb.WriteString(fmt.Sprintf("Version: %s\n", cluster.Version))
sb.WriteString(fmt.Sprintf("Cluster TotalSize: %d\n", cluster.ClusterSize))
sb.WriteString(fmt.Sprintf("License Mode: %s\n", cluster.LicenseMode))
sb.WriteString(fmt.Sprintf("Departure Count: %d\n", cluster.MembersDepartureCount))
sb.WriteString(fmt.Sprintf("Running: %v\n", cluster.Running))

return sb.String()
}
Expand All @@ -105,6 +105,9 @@ func FormatCluster(cluster config.Cluster) string {
// orderedColumns are the column names, expanded, that should be displayed first for context.
func FormatJSONForDescribe(jsonValue []byte, showAllColumns bool, orderedColumns ...string) (string, error) {
var result map[string]json.RawMessage
if len(jsonValue) == 0 {
return "", nil
}
err := json.Unmarshal(jsonValue, &result)
if err != nil {
return "", fmt.Errorf("unable to unmarshal value in FormatJSONForDescribe %v", err)
Expand Down Expand Up @@ -1318,7 +1321,7 @@ func FormatMemberHealth(health []config.HealthSummary) string {
}

// FormatMembers returns the member's information in a column formatted output.
func FormatMembers(members []config.Member, verbose bool, storageMap map[int]bool, summary bool) string {
func FormatMembers(members []config.Member, verbose bool, storageMap map[int]bool, summary bool, departureCount int) string {
var (
memberCount = len(members)
alignmentWide = []string{R, L, L, R, L, L, L, L, L, R, R, L, R, R, R}
Expand Down Expand Up @@ -1406,6 +1409,7 @@ func FormatMembers(members []config.Member, verbose bool, storageMap map[int]boo
result :=
fmt.Sprintf("Total cluster members: %d\n", memberCount) +
fmt.Sprintf("Storage enabled count: %d\n", storageCount) +
fmt.Sprintf("Departure count: %d\n\n", departureCount) +
fmt.Sprintf("Cluster Heap - Total: %s Used: %s Available: %s (%4.1f%%)\n",
strings.TrimSpace(formattingFunction(int64(totalMaxMemoryMB)*MB)),
strings.TrimSpace(formattingFunction(int64(totalUsedMB)*MB)),
Expand All @@ -1428,6 +1432,22 @@ func FormatMembers(members []config.Member, verbose bool, storageMap map[int]boo
return result
}

// FormatDepartedMembers returns the departed member's information in a column formatted output.
func FormatDepartedMembers(members []config.DepartedMembers) string {
sort.Slice(members, func(p, q int) bool {
return members[p].TimeStamp > members[q].TimeStamp
})

table := newFormattedTable().WithHeader(NodeIDColumn, "TIMESTAMP", AddressColumn, "MACHINE ID", "LOCATION", RoleColumn).
WithAlignment([]string{R, L, L, L, L, L}...)

for _, value := range members {
table.AddRow(value.NodeID, value.TimeStamp, value.Address, value.MachineID, value.Location, value.Role)
}

return table.String()
}

// FormatNetworkStatistics returns all the member's network statistics in a column formatted output.
func FormatNetworkStatistics(members []config.Member) string {
var (
Expand Down
29 changes: 26 additions & 3 deletions pkg/cmd/member.go
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023 Oracle and/or its affiliates.
* Copyright (c) 2021, 2024 Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
Expand Down Expand Up @@ -42,7 +42,8 @@ var (
p2pSortByPublisher bool
p2pSortByReceiver bool

memberSummary bool
memberSummary bool
departedMembers bool

tracingRatio float32
)
Expand Down Expand Up @@ -89,6 +90,8 @@ func getMembers(cmd *cobra.Command, networkStats bool) error {
var (
members = config.Members{}
storage = config.StorageDetails{}
cluster = config.Cluster{}
clusterResult []byte
membersResult []byte
storageResult []byte
)
Expand All @@ -103,6 +106,11 @@ func getMembers(cmd *cobra.Command, networkStats bool) error {
return err
}

clusterResult, err = dataFetcher.GetClusterDetailsJSON()
if err != nil {
return err
}

if strings.Contains(OutputFormat, constants.JSONPATH) {
result, err = utils.GetJSONPathResults(membersResult, OutputFormat)
if err != nil {
Expand All @@ -115,6 +123,7 @@ func getMembers(cmd *cobra.Command, networkStats bool) error {
printWatchHeader(cmd)

cmd.Println(FormatCurrentCluster(connection))

err = json.Unmarshal(membersResult, &members)
if err != nil {
return utils.GetError(unableToDecode, err)
Expand All @@ -125,6 +134,11 @@ func getMembers(cmd *cobra.Command, networkStats bool) error {
return utils.GetError("unable to decode storage details", err)
}

err = json.Unmarshal(clusterResult, &cluster)
if err != nil {
return utils.GetError("unable to decode cluster details", err)
}

storageMap := utils.GetStorageMap(storage)

var filteredMembers []config.Member
Expand All @@ -145,7 +159,15 @@ func getMembers(cmd *cobra.Command, networkStats bool) error {
if networkStats {
cmd.Println(FormatNetworkStatistics(filteredMembers))
} else {
cmd.Print(FormatMembers(filteredMembers, true, storageMap, memberSummary))
if departedMembers {
departedList, err1 := decodeDepartedMembers(cluster.MembersDeparted)
if err1 != nil {
return err1
}
cmd.Println(FormatDepartedMembers(departedList))
} else {
cmd.Print(FormatMembers(filteredMembers, true, storageMap, memberSummary, cluster.MembersDepartureCount))
}
}
}

Expand Down Expand Up @@ -1129,6 +1151,7 @@ func init() {

getMembersCmd.Flags().StringVarP(&roleName, "role", "r", all, roleNameDescription)
getMembersCmd.Flags().BoolVarP(&memberSummary, "summary", "S", false, "show a member summary")
getMembersCmd.Flags().BoolVarP(&departedMembers, "departed", "D", false, "show departed members only")

getNetworkStatsCmd.Flags().StringVarP(&roleName, "role", "r", all, roleNameDescription)

Expand Down
6 changes: 5 additions & 1 deletion pkg/cmd/persistence.go
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023 Oracle and/or its affiliates.
* Copyright (c) 2021, 2024 Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
Expand Down Expand Up @@ -107,6 +107,10 @@ func processPersistenceServices(deDuplicatedServices []config.ServiceSummary, da
return
}

if len(data) == 0 {
return
}

err1 = json.Unmarshal(data, &coordinator)
if err1 != nil {
errorSink.AppendError(utils.GetError("unable to unmarshall persistence coordinator", err1))
Expand Down
10 changes: 6 additions & 4 deletions pkg/cmd/service.go
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023 Oracle and/or its affiliates.
* Copyright (c) 2021, 2024 Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
Expand Down Expand Up @@ -747,9 +747,11 @@ service is a cache service.`,
return err
}

err = json.Unmarshal(coordData, &coordinator)
if err != nil {
return err
if len(coordData) > 0 {
err = json.Unmarshal(coordData, &coordinator)
if err != nil {
return err
}
}

value, err = FormatJSONForDescribe(coordData, false,
Expand Down
5 changes: 4 additions & 1 deletion pkg/cmd/snapshot.go
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023 Oracle and/or its affiliates.
* Copyright (c) 2021, 2024 Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
Expand Down Expand Up @@ -113,6 +113,9 @@ local snapshots are shown, but you can use the -a option to show archived snapsh
newSnapshots = append(newSnapshots, snapshotList...)
} else {
coordData, err = dataFetcher.GetPersistenceCoordinator(serviceNameValue)
if len(coordData) == 0 {
return
}
if err != nil {
errorSink.AppendError(err)
return
Expand Down
66 changes: 65 additions & 1 deletion pkg/cmd/utils.go
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023 Oracle and/or its affiliates.
* Copyright (c) 2021, 2024 Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/
Expand Down Expand Up @@ -606,3 +606,67 @@ func runClearCommand(cmd *cobra.Command, command string, args ...string) {
process.Stdout = cmd.OutOrStdout()
_ = process.Run()
}

func decodeDepartedMembers(members []string) ([]config.DepartedMembers, error) {
var (
membersList = make([]config.DepartedMembers, 0)
errInvalid = errors.New("invalid content")
)

const (
prefix = "Member("
suffix = ")"
)

for _, value := range members {
if !strings.HasPrefix(value, prefix) {
return nil, errInvalid
}

value = strings.Replace(value, prefix, "", 1)
if !strings.HasSuffix(value, suffix) {
return nil, errInvalid
}

value, _ = strings.CutSuffix(value, suffix)

// get the fields
v := strings.Split(value, ", ")

member := config.DepartedMembers{}

// go through each field and extract

count := 1
for _, f := range v {
s := strings.Split(f, "=")
if len(s) != 2 {
return nil, errInvalid
}

setField(&member, count, s[1])
count++
}

membersList = append(membersList, member)
}

return membersList, nil
}

func setField(member *config.DepartedMembers, field int, value string) {
switch field {
case 1:
member.NodeID = value
case 2:
member.TimeStamp = value
case 3:
member.Address = value
case 4:
member.MachineID = value
case 5:
member.Location = value
case 6:
member.Role = value
}
}
65 changes: 65 additions & 0 deletions pkg/cmd/utils_test.go
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl.
*/

package cmd

import (
. "github.com/onsi/gomega"
"github.com/oracle/coherence-cli/pkg/config"
"testing"
)

func TestDecodeMemberDetails(t *testing.T) {
var (
result []config.DepartedMembers
g = NewGomegaWithT(t)
invalid1 = []string{"rubbish"}
invalid2 = []string{"Id=4, Timestamp=2024-03-26 08:11:00.537, Address=127.0.0.1:50250, MachineId=10131, Location=machine:localhost,process:6601,member:storage-4, Role=CoherenceServer)"}
invalid3 = []string{"MemberId=4, Timestamp=2024-03-26 08:11:00.537, Address=127.0.0.1:50250, MachineId=10131, Location=machine:localhost,process:6601,member:storage-4, Role=CoherenceServer)"}
valid1 = []string{"Member(Id=4, Timestamp=2024-03-26 08:11:00.537, Address=127.0.0.1:50250, MachineId=10131, Location=machine:localhost,process:6601,member:storage-4, Role=CoherenceServer)"}
valid2 = []string{
"Member(Id=4, Timestamp=2024-03-26 08:11:00.537, Address=127.0.0.1:50250, MachineId=10131, Location=machine:localhost,process:6601,member:storage-4, Role=CoherenceServer)",
"Member(Id=3, Timestamp=2024-03-26 08:11:00.536, Address=127.0.0.1:50259, MachineId=10135, Location=machine:localhost,process:6601,member:storage-5, Role=CoherenceServer1)"}
)

_, err := decodeDepartedMembers(invalid1)
g.Expect(err).To(HaveOccurred())

_, err = decodeDepartedMembers(invalid2)
g.Expect(err).To(HaveOccurred())

_, err = decodeDepartedMembers(invalid3)
g.Expect(err).To(HaveOccurred())

result, err = decodeDepartedMembers(valid1)
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(result).To(Not(BeNil()))
g.Expect(len(result)).To(Equal(1))
g.Expect(result[0].NodeID).To(Equal("4"))
g.Expect(result[0].TimeStamp).To(Equal("2024-03-26 08:11:00.537"))
g.Expect(result[0].Address).To(Equal("127.0.0.1:50250"))
g.Expect(result[0].MachineID).To(Equal("10131"))
g.Expect(result[0].Location).To(Equal("machine:localhost,process:6601,member:storage-4"))
g.Expect(result[0].Role).To(Equal("CoherenceServer"))

result, err = decodeDepartedMembers(valid2)
g.Expect(err).To(Not(HaveOccurred()))
g.Expect(result).To(Not(BeNil()))
g.Expect(len(result)).To(Equal(2))
g.Expect(result[0].NodeID).To(Equal("4"))
g.Expect(result[0].TimeStamp).To(Equal("2024-03-26 08:11:00.537"))
g.Expect(result[0].Address).To(Equal("127.0.0.1:50250"))
g.Expect(result[0].MachineID).To(Equal("10131"))
g.Expect(result[0].Location).To(Equal("machine:localhost,process:6601,member:storage-4"))
g.Expect(result[0].Role).To(Equal("CoherenceServer"))

g.Expect(result[1].NodeID).To(Equal("3"))
g.Expect(result[1].TimeStamp).To(Equal("2024-03-26 08:11:00.536"))
g.Expect(result[1].Address).To(Equal("127.0.0.1:50259"))
g.Expect(result[1].MachineID).To(Equal("10135"))
g.Expect(result[1].Location).To(Equal("machine:localhost,process:6601,member:storage-5"))
g.Expect(result[1].Role).To(Equal("CoherenceServer1"))
}

0 comments on commit e747b6d

Please sign in to comment.