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

Add -D option to get departed members. Fix get persistence error #169

Merged
merged 1 commit into from Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"))
}