// 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") }