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
|
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// Author: arnej
// utilities to get and manipulate node states in a storage cluster
package clusterstate
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"github.com/vespa-engine/vespa/client/go/internal/admin/trace"
"github.com/vespa-engine/vespa/client/go/internal/util"
)
// common struct used various places in the clustercontroller REST api:
type StateAndReason struct {
State string `json:"state"`
Reason string `json:"reason"`
}
func (s *StateAndReason) writeTo(buf *strings.Builder) {
buf.WriteString(s.State)
if s.Reason != "" {
buf.WriteString(" [reason: ")
buf.WriteString(s.Reason)
buf.WriteString("]")
}
}
// cluster state as returned by the clustercontroller REST api:
type ClusterState struct {
State struct {
Generated StateAndReason `json:"generated"`
} `json:"state"`
Service map[string]struct {
Node map[string]struct {
Attributes struct {
HierarchicalGroup string `json:"hierarchical-group"`
} `json:"attributes"`
State struct {
Generated StateAndReason `json:"generated"`
Unit StateAndReason `json:"unit"`
User StateAndReason `json:"user"`
} `json:"state"`
Metrics struct {
BucketCount int `json:"bucket-count"`
UniqueDocumentCount int `json:"unique-document-count"`
UniqueDocumentTotalSize int `json:"unique-document-total-size"`
} `json:"metrics"`
} `json:"node"`
} `json:"service"`
DistributionStates struct {
Published struct {
Baseline string `json:"baseline"`
BucketSpaces []struct {
Name string `json:"name"`
State string `json:"state"`
} `json:"bucket-spaces"`
} `json:"published"`
} `json:"distribution-states"`
}
func (cs *ClusterState) String() string {
if cs == nil {
return "nil"
}
var buf strings.Builder
buf.WriteString("cluster state: ")
cs.State.Generated.writeTo(&buf)
for n, s := range cs.Service {
buf.WriteString("\n ")
buf.WriteString(n)
buf.WriteString(": [")
for nn, node := range s.Node {
buf.WriteString("\n ")
buf.WriteString(nn)
buf.WriteString(" -> {generated: ")
node.State.Generated.writeTo(&buf)
buf.WriteString("} {unit: ")
node.State.Unit.writeTo(&buf)
buf.WriteString("} {user: ")
node.State.User.writeTo(&buf)
buf.WriteString("}")
}
}
buf.WriteString("\n")
return buf.String()
}
func (model *VespaModelConfig) getClusterState(cluster string) (*ClusterState, *ClusterControllerSpec) {
errs := make([]string, 0, 0)
ccs := model.findClusterControllers()
if len(ccs) == 0 {
trace.Trace("No cluster controllers found in vespa model:", model)
errs = append(errs, "No cluster controllers found in vespa model config")
}
for _, cc := range ccs {
url := fmt.Sprintf("http://%s:%d/cluster/v2/%s/?recursive=true",
cc.host, cc.port, cluster)
var buf bytes.Buffer
err := curlGet(url, &buf)
if err != nil {
errs = append(errs, "could not get: "+url)
continue
}
codec := json.NewDecoder(&buf)
var parsedJson ClusterState
err = codec.Decode(&parsedJson)
if err != nil {
trace.Trace("Could not parse JSON >>>", buf.String(), "<<< from", url)
errs = append(errs, "Bad JSON from "+url+" was: "+buf.String())
continue
}
// success:
return &parsedJson, &cc
}
// no success:
util.JustExitMsg(fmt.Sprint(errs))
panic("unreachable")
}
|