Merge branch 'master' into 13937-keepstore-prometheus
[arvados.git] / services / keepstore / mounts_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "bytes"
9         "context"
10         "encoding/json"
11         "net/http"
12         "net/http/httptest"
13
14         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
15         "github.com/prometheus/client_golang/prometheus"
16         check "gopkg.in/check.v1"
17 )
18
19 var _ = check.Suite(&MountsSuite{})
20
21 type MountsSuite struct {
22         vm  VolumeManager
23         rtr http.Handler
24 }
25
26 func (s *MountsSuite) SetUpTest(c *check.C) {
27         s.vm = MakeTestVolumeManager(2)
28         KeepVM = s.vm
29         theConfig = DefaultConfig()
30         theConfig.systemAuthToken = arvadostest.DataManagerToken
31         theConfig.ManagementToken = arvadostest.ManagementToken
32         r := prometheus.NewRegistry()
33         theConfig.Start(r)
34         s.rtr = MakeRESTRouter(testCluster, r)
35 }
36
37 func (s *MountsSuite) TearDownTest(c *check.C) {
38         s.vm.Close()
39         KeepVM = nil
40         theConfig = DefaultConfig()
41         theConfig.Start(prometheus.NewRegistry())
42 }
43
44 func (s *MountsSuite) TestMounts(c *check.C) {
45         vols := s.vm.AllWritable()
46         vols[0].Put(context.Background(), TestHash, TestBlock)
47         vols[1].Put(context.Background(), TestHash2, TestBlock2)
48
49         resp := s.call("GET", "/mounts", "", nil)
50         c.Check(resp.Code, check.Equals, http.StatusOK)
51         var mntList []struct {
52                 UUID           string   `json:"uuid"`
53                 DeviceID       string   `json:"device_id"`
54                 ReadOnly       bool     `json:"read_only"`
55                 Replication    int      `json:"replication"`
56                 StorageClasses []string `json:"storage_classes"`
57         }
58         err := json.Unmarshal(resp.Body.Bytes(), &mntList)
59         c.Assert(err, check.IsNil)
60         c.Assert(len(mntList), check.Equals, 2)
61         for _, m := range mntList {
62                 c.Check(len(m.UUID), check.Equals, 27)
63                 c.Check(m.UUID[:12], check.Equals, "zzzzz-ivpuk-")
64                 c.Check(m.DeviceID, check.Equals, "mock-device-id")
65                 c.Check(m.ReadOnly, check.Equals, false)
66                 c.Check(m.Replication, check.Equals, 1)
67                 c.Check(m.StorageClasses, check.DeepEquals, []string{"default"})
68         }
69         c.Check(mntList[0].UUID, check.Not(check.Equals), mntList[1].UUID)
70
71         // Bad auth
72         for _, tok := range []string{"", "xyzzy"} {
73                 resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks", tok, nil)
74                 c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
75                 c.Check(resp.Body.String(), check.Equals, "Unauthorized\n")
76         }
77
78         tok := arvadostest.DataManagerToken
79
80         // Nonexistent mount UUID
81         resp = s.call("GET", "/mounts/X/blocks", tok, nil)
82         c.Check(resp.Code, check.Equals, http.StatusNotFound)
83         c.Check(resp.Body.String(), check.Equals, "mount not found\n")
84
85         // Complete index of first mount
86         resp = s.call("GET", "/mounts/"+mntList[0].UUID+"/blocks", tok, nil)
87         c.Check(resp.Code, check.Equals, http.StatusOK)
88         c.Check(resp.Body.String(), check.Matches, TestHash+`\+[0-9]+ [0-9]+\n\n`)
89
90         // Partial index of first mount (one block matches prefix)
91         resp = s.call("GET", "/mounts/"+mntList[0].UUID+"/blocks?prefix="+TestHash[:2], tok, nil)
92         c.Check(resp.Code, check.Equals, http.StatusOK)
93         c.Check(resp.Body.String(), check.Matches, TestHash+`\+[0-9]+ [0-9]+\n\n`)
94
95         // Complete index of second mount (note trailing slash)
96         resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks/", tok, nil)
97         c.Check(resp.Code, check.Equals, http.StatusOK)
98         c.Check(resp.Body.String(), check.Matches, TestHash2+`\+[0-9]+ [0-9]+\n\n`)
99
100         // Partial index of second mount (no blocks match prefix)
101         resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks/?prefix="+TestHash[:2], tok, nil)
102         c.Check(resp.Code, check.Equals, http.StatusOK)
103         c.Check(resp.Body.String(), check.Equals, "\n")
104 }
105
106 func (s *MountsSuite) TestMetrics(c *check.C) {
107         s.call("PUT", "/"+TestHash, "", TestBlock)
108         s.call("PUT", "/"+TestHash2, "", TestBlock2)
109         resp := s.call("GET", "/metrics.json", "", nil)
110         c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
111         resp = s.call("GET", "/metrics.json", "foobar", nil)
112         c.Check(resp.Code, check.Equals, http.StatusForbidden)
113         resp = s.call("GET", "/metrics.json", arvadostest.ManagementToken, nil)
114         c.Check(resp.Code, check.Equals, http.StatusOK)
115         var j []struct {
116                 Name   string
117                 Help   string
118                 Type   string
119                 Metric []struct {
120                         Label []struct {
121                                 Name  string
122                                 Value string
123                         }
124                         Summary struct {
125                                 SampleCount string  `json:"sample_count"`
126                                 SampleSum   float64 `json:"sample_sum"`
127                                 Quantile    []struct {
128                                         Quantile float64
129                                         Value    float64
130                                 }
131                         }
132                 }
133         }
134         json.NewDecoder(resp.Body).Decode(&j)
135         found := make(map[string]bool)
136         names := map[string]bool{}
137         for _, g := range j {
138                 names[g.Name] = true
139                 for _, m := range g.Metric {
140                         if len(m.Label) == 2 && m.Label[0].Name == "code" && m.Label[0].Value == "200" && m.Label[1].Name == "method" && m.Label[1].Value == "put" {
141                                 c.Check(m.Summary.SampleCount, check.Equals, "2")
142                                 c.Check(len(m.Summary.Quantile), check.Not(check.Equals), 0)
143                                 c.Check(m.Summary.Quantile[0].Value, check.Not(check.Equals), float64(0))
144                                 found[g.Name] = true
145                         }
146                 }
147         }
148         c.Check(found["request_duration_seconds"], check.Equals, true)
149         c.Check(found["time_to_status_seconds"], check.Equals, true)
150
151         metricsNames := []string{
152                 "arvados_keepstore_bufferpool_buffers_in_use",
153                 "arvados_keepstore_bufferpool_buffers_max",
154                 "arvados_keepstore_bufferpool_bytes_allocated",
155                 "arvados_keepstore_pull_queue_in_progress",
156                 "arvados_keepstore_pull_queue_queued",
157                 "arvados_keepstore_requests_current",
158                 "arvados_keepstore_requests_max",
159                 "arvados_keepstore_trash_queue_in_progress",
160                 "arvados_keepstore_trash_queue_queued",
161                 "request_duration_seconds",
162                 "time_to_status_seconds",
163         }
164         for _, m := range metricsNames {
165                 _, ok := names[m]
166                 c.Check(ok, check.Equals, true)
167         }
168 }
169
170 func (s *MountsSuite) call(method, path, tok string, body []byte) *httptest.ResponseRecorder {
171         resp := httptest.NewRecorder()
172         req, _ := http.NewRequest(method, path, bytes.NewReader(body))
173         if tok != "" {
174                 req.Header.Set("Authorization", "Bearer "+tok)
175         }
176         s.rtr.ServeHTTP(resp, req)
177         return resp
178 }