blob: 0e2875583c6695b06de6c6f015de350b9dd6483f [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanski6f599512023-04-26 19:08:19 +02004package scruffy
5
6import (
7 "context"
8 "fmt"
9 "sort"
10 "strings"
11 "testing"
12
13 "github.com/prometheus/client_golang/prometheus"
14
15 "source.monogon.dev/cloud/bmaas/bmdb"
16 "source.monogon.dev/cloud/bmaas/bmdb/model"
17 "source.monogon.dev/cloud/lib/component"
18)
19
20func TestBMDBStats(t *testing.T) {
21 s := Server{
22 Config: Config{
23 BMDB: bmdb.BMDB{
24 Config: bmdb.Config{
25 Database: component.CockroachConfig{
26 InMemory: true,
27 },
28 },
29 },
30 },
31 }
32
33 registry := prometheus.NewRegistry()
34 runner := newBMDBStatsRunner(&s, registry)
35
36 ctx, ctxC := context.WithCancel(context.Background())
37 defer ctxC()
38
39 expect := func(wantValues map[string]int64) {
40 t.Helper()
41 res, err := registry.Gather()
42 if err != nil {
43 t.Fatalf("Gather: %v", err)
44 }
45 gotValues := make(map[string]bool)
46 for _, mf := range res {
47 if len(mf.Metric) != 1 {
48 for _, m := range mf.Metric {
49 var lvs []string
50 for _, lp := range m.Label {
51 lvs = append(lvs, fmt.Sprintf("%s=%s", *lp.Name, *lp.Value))
52 }
53 sort.Strings(lvs)
54 name := fmt.Sprintf("%s[%s]", *mf.Name, strings.Join(lvs, ","))
55 gotValues[name] = true
56 if _, ok := wantValues[name]; !ok {
57 t.Errorf("MetricFamily %s: unexpected", name)
58 }
59 if want, got := wantValues[name], int64(*m.Gauge.Value); want != got {
60 t.Errorf("MetricFamily %s: wanted %d, got %d", *mf.Name, want, got)
61 }
62 }
63 } else {
64 m := mf.Metric[0]
65 gotValues[*mf.Name] = true
66 if want, got := wantValues[*mf.Name], int64(*m.Gauge.Value); want != got {
67 t.Errorf("MetricFamily %s: wanted %d, got %d", *mf.Name, want, got)
68 }
69 if _, ok := wantValues[*mf.Name]; !ok {
70 t.Errorf("MetricFamily %s: unexpected", *mf.Name)
71 }
72 }
73 }
Tim Windelschmidt6b6428d2024-04-11 01:35:41 +020074 for mf := range wantValues {
Serge Bazanski6f599512023-04-26 19:08:19 +020075 if !gotValues[mf] {
76 t.Errorf("MetricFamily %s: missing", mf)
77 }
78 }
79 }
80
81 expect(map[string]int64{
82 "bmdb_stats_machines": 0,
83 "bmdb_stats_machines_provided": 0,
84 "bmdb_stats_machines_heartbeating": 0,
85 "bmdb_stats_machines_pending_installation": 0,
86 "bmdb_stats_machines_installed": 0,
87 "bmdb_stats_machines_pending_agent_start": 0,
88 "bmdb_stats_machines_pending_agent_recovery": 0,
89 "bmdb_stats_active_backoffs[process=ShepherdAccess]": 0,
90 "bmdb_stats_active_backoffs[process=ShepherdAgentStart]": 0,
91 "bmdb_stats_active_backoffs[process=ShepherdRecovery]": 0,
92 "bmdb_stats_active_work[process=ShepherdAccess]": 0,
93 "bmdb_stats_active_work[process=ShepherdAgentStart]": 0,
94 "bmdb_stats_active_work[process=ShepherdRecovery]": 0,
95 })
96
97 conn, err := s.Config.BMDB.Open(true)
98 if err != nil {
99 t.Fatalf("Open: %v", err)
100 }
101 sess, err := conn.StartSession(ctx)
102 if err != nil {
103 t.Fatalf("StartSession: %v", err)
104 }
105
106 s.bmdb = conn
107 s.sessionC = make(chan *bmdb.Session)
108 go s.sessionWorker(ctx)
109 if err := runner.runOnce(ctx); err != nil {
110 t.Fatal(err)
111 }
112
113 expect(map[string]int64{
114 "bmdb_stats_machines": 0,
115 "bmdb_stats_machines_provided": 0,
116 "bmdb_stats_machines_heartbeating": 0,
117 "bmdb_stats_machines_pending_installation": 0,
118 "bmdb_stats_machines_installed": 0,
119 "bmdb_stats_machines_pending_agent_start": 0,
120 "bmdb_stats_machines_pending_agent_recovery": 0,
121 "bmdb_stats_active_backoffs[process=ShepherdAccess]": 0,
122 "bmdb_stats_active_backoffs[process=ShepherdAgentStart]": 0,
123 "bmdb_stats_active_backoffs[process=ShepherdRecovery]": 0,
124 "bmdb_stats_active_work[process=ShepherdAccess]": 0,
125 "bmdb_stats_active_work[process=ShepherdAgentStart]": 0,
126 "bmdb_stats_active_work[process=ShepherdRecovery]": 0,
127 })
128
129 f := fill().
130 // Provided, needs installation.
131 machine().providedE("1").build().
132 // Three machines needing recovery.
133 machine().providedE("2").agentNeverHeartbeat().build().
134 machine().providedE("3").agentNeverHeartbeat().build().
135 machine().providedE("4").agentNeverHeartbeat().build().
136 // One machine correctly heartbeating.
137 machine().providedE("5").agentHealthy().build().
138 // Two machines heartbeating and pending installation.
139 machine().providedE("6").agentHealthy().installRequested(10).build().
140 machine().providedE("7").agentHealthy().installRequested(10).installReported(9).build().
141 // Machine which is pending installation _and_ recovery.
142 machine().providedE("8").agentNeverHeartbeat().installRequested(10).build().
143 // Machine which has been successfully installed.
144 machine().providedE("9").agentStoppedHeartbeating().installRequested(10).installReported(10).build()
145
146 err = sess.Transact(ctx, func(q *model.Queries) error {
147 return f(ctx, q)
148 })
149 if err != nil {
150 t.Fatal(err)
151 }
152
153 if err := runner.runOnce(ctx); err != nil {
154 t.Fatal(err)
155 }
156
157 expect(map[string]int64{
158 "bmdb_stats_machines": 9,
159 "bmdb_stats_machines_provided": 9,
160 "bmdb_stats_machines_heartbeating": 3,
161 "bmdb_stats_machines_pending_installation": 3,
162 "bmdb_stats_machines_installed": 1,
163 "bmdb_stats_machines_pending_agent_start": 1,
164 "bmdb_stats_machines_pending_agent_recovery": 4,
165 "bmdb_stats_active_backoffs[process=ShepherdAccess]": 0,
166 "bmdb_stats_active_backoffs[process=ShepherdAgentStart]": 0,
167 "bmdb_stats_active_backoffs[process=ShepherdRecovery]": 0,
168 "bmdb_stats_active_work[process=ShepherdAccess]": 0,
169 "bmdb_stats_active_work[process=ShepherdAgentStart]": 0,
170 "bmdb_stats_active_work[process=ShepherdRecovery]": 0,
171 })
172}