blob: c583f3a1403b7296b7df35d1ea24a2d8f5d21d02 [file] [log] [blame]
Serge Bazanski54e212a2023-06-14 13:45:11 +02001package metrics
2
3import (
4 "context"
5 "crypto/tls"
6 "crypto/x509"
7 "fmt"
8 "io"
9 "net/http"
10 "net/url"
11 "strings"
12 "testing"
13 "time"
14
Tim Windelschmidtb551b652023-07-17 16:01:42 +020015 "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
16
17 apb "source.monogon.dev/metropolis/node/core/curator/proto/api"
18 cpb "source.monogon.dev/metropolis/proto/common"
19
Serge Bazanski54e212a2023-06-14 13:45:11 +020020 "source.monogon.dev/metropolis/cli/pkg/datafile"
21 "source.monogon.dev/metropolis/node"
Tim Windelschmidtb551b652023-07-17 16:01:42 +020022 "source.monogon.dev/metropolis/pkg/event/memory"
Serge Bazanski54e212a2023-06-14 13:45:11 +020023 "source.monogon.dev/metropolis/pkg/supervisor"
24 "source.monogon.dev/metropolis/test/util"
25)
26
27// TestMetricsForwarder exercises the metrics forwarding functionality of the
28// metrics service. That is, it makes sure that the service starts some fake
29// exporters and then forwards HTTP traffic to them.
30func TestMetricsForwarder(t *testing.T) {
31 path, _ := datafile.ResolveRunfile("metropolis/node/core/metrics/fake_exporter/fake_exporter_/fake_exporter")
32
33 exporters := []Exporter{
34 {
35 Name: "test1",
36 Port: node.Port(8081),
37 Executable: path,
38 Arguments: []string{
39 "-listen", "127.0.0.1:8081",
40 "-value", "100",
41 },
42 },
43 {
44 Name: "test2",
45 Port: node.Port(8082),
46 Executable: path,
47 Arguments: []string{
48 "-listen", "127.0.0.1:8082",
49 "-value", "200",
50 },
51 },
52 }
53
Serge Bazanskiffbf3932023-07-24 13:02:42 +020054 eph := util.NewEphemeralClusterCredentials(t, 1)
Serge Bazanski54e212a2023-06-14 13:45:11 +020055
56 svc := Service{
57 Credentials: eph.Nodes[0],
58 Exporters: exporters,
59
60 enableDynamicAddr: true,
61 dynamicAddr: make(chan string),
62 }
63
64 supervisor.TestHarness(t, svc.Run)
65 addr := <-svc.dynamicAddr
66
67 pool := x509.NewCertPool()
68 pool.AddCert(eph.CA)
69
70 cl := http.Client{
71 Transport: &http.Transport{
72 TLSClientConfig: &tls.Config{
73 ServerName: eph.Nodes[0].ID(),
74 RootCAs: pool,
75
76 Certificates: []tls.Certificate{eph.Manager},
77 },
78 },
79 }
80
81 ctx, ctxC := context.WithCancel(context.Background())
82 defer ctxC()
83
84 util.TestEventual(t, "retrieve-test1", ctx, 10*time.Second, func(ctx context.Context) error {
85 url := (&url.URL{
86 Scheme: "https",
87 Host: addr,
88 Path: "/metrics/test1",
89 }).String()
90 req, _ := http.NewRequest("GET", url, nil)
91 res, err := cl.Do(req)
92 if err != nil {
93 return fmt.Errorf("Get(%q): %v", url, err)
94 }
95 defer res.Body.Close()
96 if res.StatusCode != 200 {
97 return fmt.Errorf("Get(%q): code %d", url, res.StatusCode)
98 }
99 body, _ := io.ReadAll(res.Body)
100 want := "test 100"
101 if !strings.Contains(string(body), want) {
102 return util.Permanent(fmt.Errorf("did not find expected value %q in %q", want, string(body)))
103 }
104 return nil
105 })
106 util.TestEventual(t, "retrieve-test2", ctx, 10*time.Second, func(ctx context.Context) error {
107 url := (&url.URL{
108 Scheme: "https",
109 Host: addr,
110 Path: "/metrics/test2",
111 }).String()
112 req, _ := http.NewRequest("GET", url, nil)
113 res, err := cl.Do(req)
114 if err != nil {
115 return fmt.Errorf("Get(%q): %v", url, err)
116 }
117 defer res.Body.Close()
118 if res.StatusCode != 200 {
119 return fmt.Errorf("Get(%q): code %d", url, res.StatusCode)
120 }
121 body, _ := io.ReadAll(res.Body)
122 want := "test 200"
123 if !strings.Contains(string(body), want) {
124 return util.Permanent(fmt.Errorf("did not find expected value %q in %q", want, string(body)))
125 }
126 return nil
127 })
128}
Tim Windelschmidtb551b652023-07-17 16:01:42 +0200129
130func TestDiscovery(t *testing.T) {
131 eph := util.NewEphemeralClusterCredentials(t, 1)
132
133 curator, ccl := util.MakeTestCurator(t)
134 defer ccl.Close()
135
136 svc := Service{
137 Credentials: eph.Nodes[0],
138 Curator: apb.NewCuratorClient(ccl),
139 LocalRoles: &memory.Value[*cpb.NodeRoles]{},
140 Exporters: []Exporter{},
141 enableDynamicAddr: true,
142 dynamicAddr: make(chan string),
143 }
144
145 supervisor.TestHarness(t, svc.Run)
146 addr := <-svc.dynamicAddr
147
148 pool := x509.NewCertPool()
149 pool.AddCert(eph.CA)
150
151 cl := http.Client{
152 Transport: &http.Transport{
153 TLSClientConfig: &tls.Config{
154 ServerName: eph.Nodes[0].ID(),
155 RootCAs: pool,
156
157 Certificates: []tls.Certificate{eph.Manager},
158 },
159 },
160 }
161
162 ctx, ctxC := context.WithCancel(context.Background())
163 defer ctxC()
164
165 util.TestEventual(t, "inactive-discovery", ctx, 10*time.Second, func(ctx context.Context) error {
166 url := (&url.URL{
167 Scheme: "https",
168 Host: addr,
169 Path: "/discovery",
170 }).String()
171 req, _ := http.NewRequest("GET", url, nil)
172 res, err := cl.Do(req)
173 if err != nil {
174 return fmt.Errorf("Get(%q): %v", url, err)
175 }
176 defer res.Body.Close()
177 if res.StatusCode != http.StatusNotImplemented {
178 return fmt.Errorf("Get(%q): code %d", url, res.StatusCode)
179 }
180 return nil
181 })
182
183 // First set the local roles to be a consensus member which starts a watcher,
184 // create a fake node after that
185 svc.LocalRoles.Set(&cpb.NodeRoles{ConsensusMember: &cpb.NodeRoles_ConsensusMember{}})
186 curator.NodeWithPrefixes(wgtypes.Key{}, "metropolis-fake-1", "1.2.3.4")
187
188 util.TestEventual(t, "active-discovery", ctx, 10*time.Second, func(ctx context.Context) error {
189 url := (&url.URL{
190 Scheme: "https",
191 Host: addr,
192 Path: "/discovery",
193 }).String()
194 req, _ := http.NewRequest("GET", url, nil)
195 res, err := cl.Do(req)
196 if err != nil {
197 return fmt.Errorf("Get(%q): %v", url, err)
198 }
199 defer res.Body.Close()
200 if res.StatusCode != http.StatusOK {
201 return fmt.Errorf("Get(%q): code %d", url, res.StatusCode)
202 }
203 body, _ := io.ReadAll(res.Body)
Tim Windelschmidt4c6720d2023-07-25 14:44:19 +0000204 want := `[{"targets":["1.2.3.4"],"labels":{"__meta_metropolis_role_consensus_member":"true","__meta_metropolis_role_kubernetes_controller":"false","__meta_metropolis_role_kubernetes_worker":"false"}}]`
Tim Windelschmidtb551b652023-07-17 16:01:42 +0200205 if !strings.Contains(string(body), want) {
206 return util.Permanent(fmt.Errorf("did not find expected value %q in %q", want, string(body)))
207 }
208 return nil
209 })
210}