internal/cli/status.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package cli
import (
"bytes"
"flag"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"time"
"j3s.sh/cascade/api"
)
type statusCommand struct {
flagAPIAddr string
}
func (c statusCommand) Usage() {
fmt.Printf(`usage: cascade status [flags]
high-level overview of the cascade cluster: member counts,
status breakdown, and agent reachability.
flags:
-api
address of the cascade http api to target (default = 127.0.0.1:8500)
`)
}
func (c *statusCommand) Init(args []string) {
flags := flag.NewFlagSet("", flag.ContinueOnError)
flags.Usage = c.Usage
flags.StringVar(&c.flagAPIAddr, "api", "", "")
if err := flags.Parse(args); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func RunStatus(args []string) {
c := statusCommand{}
c.Init(args)
cfg := api.DefaultConfig()
if c.flagAPIAddr != "" {
cfg.Address = c.flagAPIAddr
}
client, err := api.NewClient(cfg)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
agent := client.Agent()
start := time.Now()
nodes, err := agent.Members()
apiRTT := time.Since(start)
if err != nil {
fmt.Printf("error fetching nodes from %s: %s\n", cfg.Address, err)
os.Exit(1)
}
self, err := agent.SelfTyped()
nodeName := "<unknown>"
var dnsAddr, httpAddr, serfAddr, dnsDomain string
if err == nil {
nodeName = self.Config.NodeName
dnsAddr = self.Config.DNSBindAddr
httpAddr = self.Config.HTTPBindAddr
serfAddr = self.Config.SerfBindAddr
dnsDomain = self.Config.DNSDomain
}
statusCounts := map[string]int{}
for _, n := range nodes {
statusCounts[n.StatusPretty()]++
}
localServices, _ := agent.Services()
catalogServices, _ := client.Catalog().Services()
total := len(nodes)
alive := statusCounts["alive"]
healthPct := 0.0
if total > 0 {
healthPct = 100 * float64(alive) / float64(total)
}
var b bytes.Buffer
tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
fmt.Fprintln(tw, "cluster")
fmt.Fprintf(tw, " agent\t%s\n", nodeName)
fmt.Fprintf(tw, " api\t%s\n", cfg.Address)
fmt.Fprintf(tw, " api rtt\t%s\n", apiRTT.Round(time.Microsecond))
fmt.Fprintf(tw, " nodes\t%d\n", total)
fmt.Fprintf(tw, " health\t%.1f%% alive (%d/%d)\n", healthPct, alive, total)
fmt.Fprintln(tw)
if dnsAddr != "" || httpAddr != "" || serfAddr != "" {
fmt.Fprintln(tw, "bind addrs")
fmt.Fprintf(tw, " dns\t%s\n", dnsAddr)
fmt.Fprintf(tw, " http\t%s\n", httpAddr)
fmt.Fprintf(tw, " serf\t%s\n", serfAddr)
if dnsDomain != "" {
fmt.Fprintf(tw, " domain\t.%s\n", dnsDomain)
}
fmt.Fprintln(tw)
}
fmt.Fprintln(tw, "services")
fmt.Fprintf(tw, " local agent\t%s\n", summarizeLocalServices(localServices))
fmt.Fprintf(tw, " cluster\t%s\n", summarizeCatalog(catalogServices))
fmt.Fprintln(tw)
fmt.Fprintln(tw, "status")
for _, s := range sortedKeys(statusCounts) {
fmt.Fprintf(tw, " %s\t%d\n", s, statusCounts[s])
}
if err := tw.Flush(); err != nil {
fmt.Printf("error flushing tabwriter: %s", err)
os.Exit(1)
}
fmt.Print(b.String())
if alive < total {
os.Exit(1)
}
}
func sortedKeys(m map[string]int) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
sort.Strings(out)
return out
}
func summarizeLocalServices(services map[string]*api.AgentService) string {
if len(services) == 0 {
return "none"
}
names := make([]string, 0, len(services))
for _, s := range services {
names = append(names, s.Service)
}
sort.Strings(names)
return fmt.Sprintf("%d (%s)", len(names), strings.Join(names, ", "))
}
func summarizeCatalog(services map[string][]string) string {
if len(services) == 0 {
return "none"
}
return fmt.Sprintf("%d unique services", len(services))
}