| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 1 | package metrics |
| 2 | |
| 3 | import ( |
| 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 Windelschmidt | b551b65 | 2023-07-17 16:01:42 +0200 | [diff] [blame] | 15 | "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 Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 20 | "source.monogon.dev/metropolis/cli/pkg/datafile" |
| 21 | "source.monogon.dev/metropolis/node" |
| Tim Windelschmidt | b551b65 | 2023-07-17 16:01:42 +0200 | [diff] [blame] | 22 | "source.monogon.dev/metropolis/pkg/event/memory" |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 23 | "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. |
| 30 | func 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 Bazanski | ffbf393 | 2023-07-24 13:02:42 +0200 | [diff] [blame] | 54 | eph := util.NewEphemeralClusterCredentials(t, 1) |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 55 | |
| 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 Windelschmidt | b551b65 | 2023-07-17 16:01:42 +0200 | [diff] [blame] | 129 | |
| 130 | func 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 Windelschmidt | 4c6720d | 2023-07-25 14:44:19 +0000 | [diff] [blame] | 204 | 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 Windelschmidt | b551b65 | 2023-07-17 16:01:42 +0200 | [diff] [blame] | 205 | 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 | } |