Skip to content
New issue

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

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

Already on GitHub? # to your account

Support JSON output in stolonctl status #628

Merged
merged 1 commit into from
Apr 13, 2019
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 207 additions & 77 deletions cmd/stolonctl/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package cmd

import (
"context"
"encoding/json"
"fmt"
"os"
"sort"
Expand All @@ -34,13 +35,160 @@ var cmdStatus = &cobra.Command{
Short: "Display the current cluster status",
}

type StatusOptions struct {
Format string
}

var statusOpts StatusOptions

func init() {
cmdStatus.PersistentFlags().StringVarP(&statusOpts.Format, "format", "f", "", "output format")
CmdStolonCtl.AddCommand(cmdStatus)
}

type Status struct {
Sentinels []SentinelStatus `json:"sentinels"`
Proxies []ProxyStatus `json:"proxies"`
Keepers []KeeperStatus `json:"keepers"`
Cluster ClusterStatus `json:"cluster"`
}

type SentinelStatus struct {
UID string `json:"uid"`
Leader bool `json:"leader"`
}

type ProxyStatus struct {
UID string `json:"uid"`
Generation int64 `json:"generation"`
}

type KeeperStatus struct {
UID string `json:"uid"`
ListenAddress string `json:"listen_address"`
Healthy bool `json:"healthy"`
PgHealthy bool `json:"pg_healthy"`
PgWantedGeneration int64 `json:"pg_wanted_generation"`
PgCurrentGeneration int64 `json:"pg_current_generation"`
}

type ClusterStatus struct {
Available bool `json:"available"`
MasterKeeperUID string `json:"master_keeper_uid"`
MasterDBUID string `json:"master_db_uid"`
}

func status(cmd *cobra.Command, args []string) {
status, generateErr := generateStatus()
switch statusOpts.Format {
case "json":
renderJSON(status, generateErr)
case "text":
renderText(status, generateErr)
case "":
renderText(status, generateErr)
default:
die("unrecognised output format %s", statusOpts.Format)
}
}

func renderJSON(status Status, generateErr error) {
if generateErr != nil {
marshalJSON(generateErr)
} else {
marshalJSON(status)
}
}

func marshalJSON(value interface{}) {
output, err := json.MarshalIndent(value, "", "\t")
if err != nil {
die("failed to marshal error: %v", err)
}
stdout("%s", output)
}

func renderText(status Status, generateErr error) {
if generateErr != nil {
die("%v", generateErr)
}

tabOut := new(tabwriter.Writer)
tabOut.Init(os.Stdout, 0, 8, 1, '\t', 0)

stdout("=== Active sentinels ===")
stdout("")
if len(status.Sentinels) == 0 {
stdout("No active sentinels")
} else {
fmt.Fprintf(tabOut, "ID\tLEADER\n")
for _, s := range status.Sentinels {
fmt.Fprintf(tabOut, "%s\t%t\n", s.UID, s.Leader)
tabOut.Flush()
}
}

stdout("")
stdout("=== Active proxies ===")
stdout("")
if len(status.Proxies) == 0 {
stdout("No active proxies")
} else {
fmt.Fprintf(tabOut, "ID\n")
for _, p := range status.Proxies {
fmt.Fprintf(tabOut, "%s\n", p.UID)
tabOut.Flush()
}
}

stdout("")
stdout("=== Keepers ===")
stdout("")
if len(status.Keepers) == 0 {
stdout("No keepers available")
stdout("")
} else {
fmt.Fprintf(tabOut, "UID\tHEALTHY\tPG LISTENADDRESS\tPG HEALTHY\tPG WANTEDGENERATION\tPG CURRENTGENERATION\n")
for _, k := range status.Keepers {
fmt.Fprintf(tabOut, "%s\t%t\t%s\t%t\t%d\t%d\t\n", k.UID, k.Healthy, k.ListenAddress, k.PgHealthy, k.PgWantedGeneration, k.PgCurrentGeneration)
tabOut.Flush()
}
}

if status.Cluster.MasterKeeperUID == "" {
stdout("No cluster available")
} else {
stdout("")
stdout("=== Cluster Info ===")
stdout("")
if status.Cluster.MasterKeeperUID != "" {
stdout("Master Keeper: %s", status.Cluster.MasterKeeperUID)
} else {
stdout("Master Keeper: (none)")
}
}

// This tree data isn't currently available in the Status struct
e, err := cmdcommon.NewStore(&cfg.CommonConfig)
if err != nil {
die("%v", err)
}
cd, _, err := getClusterData(e)
if err != nil {
die("%v", err)
}
if status.Cluster.MasterDBUID != "" {
stdout("")
stdout("===== Keepers/DB tree =====")
stdout("")
printTree(status.Cluster.MasterDBUID, cd, 0, "", true)
}
stdout("")
}

func printTree(dbuid string, cd *cluster.ClusterData, level int, prefix string, tail bool) {
// skip not existing db: specified as a follower but not available in the
// clister spec (this should happen only when doing a stolonctl
// cluster spec (this should happen only when doing a stolonctl
// removekeeper)
if _, ok := cd.DBs[dbuid]; !ok {
return
Expand Down Expand Up @@ -83,121 +231,103 @@ func printTree(dbuid string, cd *cluster.ClusterData, level int, prefix string,
}
}

func status(cmd *cobra.Command, args []string) {
func generateStatus() (Status, error) {
status := Status{}
tabOut := new(tabwriter.Writer)
tabOut.Init(os.Stdout, 0, 8, 1, '\t', 0)

e, err := cmdcommon.NewStore(&cfg.CommonConfig)
if err != nil {
die("%v", err)
return status, err
}

election, err := cmdcommon.NewElection(&cfg.CommonConfig, "")
if err != nil {
die("cannot create election: %v", err)
return status, err
}

lsid, err := election.Leader()
if err != nil && err != store.ErrElectionNoLeader {
die("cannot get leader sentinel info: %v", err)
return status, err
}

sentinelsInfo, err := e.GetSentinelsInfo(context.TODO())
if err != nil {
die("cannot create election: %v", err)
return status, err
}

stdout("=== Active sentinels ===")
stdout("")
if len(sentinelsInfo) == 0 {
stdout("No active sentinels")
} else {
sort.Sort(sentinelsInfo)
fmt.Fprintf(tabOut, "ID\tLEADER\n")
for _, si := range sentinelsInfo {
leader := false
if lsid != "" {
if si.UID == lsid {
leader = true
}
}
fmt.Fprintf(tabOut, "%s\t%t\n", si.UID, leader)
tabOut.Flush()
}
sentinels := make([]SentinelStatus, 0)
sort.Sort(sentinelsInfo)
for _, si := range sentinelsInfo {
leader := lsid != "" && si.UID == lsid
sentinels = append(sentinels, SentinelStatus{UID: si.UID, Leader: leader})
}
status.Sentinels = sentinels

proxiesInfo, err := e.GetProxiesInfo(context.TODO())
if err != nil {
die("cannot get proxies info: %v", err)
return status, err
}
proxiesInfoSlice := proxiesInfo.ToSlice()

stdout("")
stdout("=== Active proxies ===")
stdout("")
if len(proxiesInfo) == 0 {
stdout("No active proxies")
} else {
sort.Sort(proxiesInfoSlice)
fmt.Fprintf(tabOut, "ID\n")
for _, pi := range proxiesInfoSlice {
fmt.Fprintf(tabOut, "%s\n", pi.UID)
tabOut.Flush()
}
proxies := make([]ProxyStatus, 0)
sort.Sort(proxiesInfoSlice)
for _, pi := range proxiesInfoSlice {
proxies = append(proxies, ProxyStatus{UID: pi.UID, Generation: pi.Generation})
}
status.Proxies = proxies

cd, _, err := getClusterData(e)
if err != nil {
die("%v", err)
return status, err
}

stdout("")
stdout("=== Keepers ===")
stdout("")
if cd.Keepers == nil {
stdout("No keepers available")
stdout("")
} else {
kssKeys := cd.Keepers.SortedKeys()
fmt.Fprintf(tabOut, "UID\tHEALTHY\tPG LISTENADDRESS\tPG HEALTHY\tPG WANTEDGENERATION\tPG CURRENTGENERATION\n")
for _, kuid := range kssKeys {
k := cd.Keepers[kuid]
db := cd.FindDB(k)
if db != nil {
dbListenAddress := "(unknown)"
if db.Status.ListenAddress != "" {
dbListenAddress = fmt.Sprintf("%s:%s", db.Status.ListenAddress, db.Status.Port)
}
fmt.Fprintf(tabOut, "%s\t%t\t%s\t%t\t%d\t%d\t\n", k.UID, k.Status.Healthy, dbListenAddress, db.Status.Healthy, db.Generation, db.Status.CurrentGeneration)
} else {
fmt.Fprintf(tabOut, "%s\t%t\t(no db assigned)\t\t\t\t\n", k.UID, k.Status.Healthy)
keepers := make([]KeeperStatus, 0)
kssKeys := cd.Keepers.SortedKeys()
for _, kuid := range kssKeys {
k := cd.Keepers[kuid]
db := cd.FindDB(k)
dbListenAddress := "(no db assigned)"
var (
pgHealthy bool
pgCurrentGeneration int64
pgWantedGeneration int64
)
if db != nil {
pgHealthy = db.Status.Healthy
pgCurrentGeneration = db.Status.CurrentGeneration
pgWantedGeneration = db.Generation

dbListenAddress = "(unknown)"
if db.Status.ListenAddress != "" {
dbListenAddress = fmt.Sprintf("%s:%s", db.Status.ListenAddress, db.Status.Port)
}
}
keeper := KeeperStatus{
UID: kuid,
ListenAddress: dbListenAddress,
Healthy: k.Status.Healthy,
PgHealthy: pgHealthy,
PgWantedGeneration: pgWantedGeneration,
PgCurrentGeneration: pgCurrentGeneration,
}
keepers = append(keepers, keeper)
}
tabOut.Flush()
status.Keepers = keepers

cluster := ClusterStatus{}
if cd.Cluster == nil || cd.DBs == nil {
stdout("No cluster available")
return
}

master := cd.Cluster.Status.Master
stdout("")
stdout("=== Cluster Info ===")
stdout("")
if master != "" {
stdout("Master: %s", cd.Keepers[cd.DBs[master].Spec.KeeperUID].UID)
cluster.Available = false
} else {
stdout("Master Keeper: (none)")
}
master := cd.Cluster.Status.Master
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cluster.Available should be set to true here or it'll always be false

cluster.Available = true

if master != "" {
stdout("")
stdout("===== Keepers/DB tree =====")
masterDB := cd.DBs[master]
stdout("")
printTree(masterDB.UID, cd, 0, "", true)
if master != "" {
cluster.MasterDBUID = cd.DBs[master].UID
cluster.MasterKeeperUID = cd.Keepers[cd.DBs[master].Spec.KeeperUID].UID
}
}
status.Cluster = cluster

stdout("")
return status, nil
}
5 changes: 3 additions & 2 deletions doc/commands/stolonctl_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ stolonctl status [flags]
### Options

```
-h, --help help for status
-f, --format string output format
-h, --help help for status
```

### Options inherited from parent commands
Expand All @@ -39,4 +40,4 @@ stolonctl status [flags]

* [stolonctl](stolonctl.md) - stolon command line client

###### Auto generated by spf13/cobra on 21-Aug-2018
###### Auto generated by spf13/cobra on 27-Mar-2019