aboutsummaryrefslogtreecommitdiffstats
path: root/client/go/internal/admin/clusterstate/cluster_state.go
blob: 7317e9a8a3a389de1e9eefa16c0e649d44b4de2f (plain) (blame)
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 Yahoo. 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")
}