Merge branch '14285-keep-balance-metrics'
[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         check "gopkg.in/check.v1"
16 )
17
18 var _ = check.Suite(&MountsSuite{})
19
20 type MountsSuite struct {
21         vm  VolumeManager
22         rtr http.Handler
23 }
24
25 func (s *MountsSuite) SetUpTest(c *check.C) {
26         s.vm = MakeTestVolumeManager(2)
27         KeepVM = s.vm
28         theConfig = DefaultConfig()
29         theConfig.systemAuthToken = arvadostest.DataManagerToken
30         theConfig.ManagementToken = arvadostest.ManagementToken
31         theConfig.Start()
32         s.rtr = MakeRESTRouter(testCluster)
33 }
34
35 func (s *MountsSuite) TearDownTest(c *check.C) {
36         s.vm.Close()
37         KeepVM = nil
38         theConfig = DefaultConfig()
39         theConfig.Start()
40 }
41
42 func (s *MountsSuite) TestMounts(c *check.C) {
43         vols := s.vm.AllWritable()
44         vols[0].Put(context.Background(), TestHash, TestBlock)
45         vols[1].Put(context.Background(), TestHash2, TestBlock2)
46
47         resp := s.call("GET", "/mounts", "", nil)
48         c.Check(resp.Code, check.Equals, http.StatusOK)
49         var mntList []struct {
50                 UUID           string   `json:"uuid"`
51                 DeviceID       string   `json:"device_id"`
52                 ReadOnly       bool     `json:"read_only"`
53                 Replication    int      `json:"replication"`
54                 StorageClasses []string `json:"storage_classes"`
55         }
56         err := json.Unmarshal(resp.Body.Bytes(), &mntList)
57         c.Assert(err, check.IsNil)
58         c.Assert(len(mntList), check.Equals, 2)
59         for _, m := range mntList {
60                 c.Check(len(m.UUID), check.Equals, 27)
61                 c.Check(m.UUID[:12], check.Equals, "zzzzz-ivpuk-")
62                 c.Check(m.DeviceID, check.Equals, "mock-device-id")
63                 c.Check(m.ReadOnly, check.Equals, false)
64                 c.Check(m.Replication, check.Equals, 1)
65                 c.Check(m.StorageClasses, check.DeepEquals, []string{"default"})
66         }
67         c.Check(mntList[0].UUID, check.Not(check.Equals), mntList[1].UUID)
68
69         // Bad auth
70         for _, tok := range []string{"", "xyzzy"} {
71                 resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks", tok, nil)
72                 c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
73                 c.Check(resp.Body.String(), check.Equals, "Unauthorized\n")
74         }
75
76         tok := arvadostest.DataManagerToken
77
78         // Nonexistent mount UUID
79         resp = s.call("GET", "/mounts/X/blocks", tok, nil)
80         c.Check(resp.Code, check.Equals, http.StatusNotFound)
81         c.Check(resp.Body.String(), check.Equals, "mount not found\n")
82
83         // Complete index of first mount
84         resp = s.call("GET", "/mounts/"+mntList[0].UUID+"/blocks", tok, nil)
85         c.Check(resp.Code, check.Equals, http.StatusOK)
86         c.Check(resp.Body.String(), check.Matches, TestHash+`\+[0-9]+ [0-9]+\n\n`)
87
88         // Partial index of first mount (one block matches prefix)
89         resp = s.call("GET", "/mounts/"+mntList[0].UUID+"/blocks?prefix="+TestHash[:2], tok, nil)
90         c.Check(resp.Code, check.Equals, http.StatusOK)
91         c.Check(resp.Body.String(), check.Matches, TestHash+`\+[0-9]+ [0-9]+\n\n`)
92
93         // Complete index of second mount (note trailing slash)
94         resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks/", tok, nil)
95         c.Check(resp.Code, check.Equals, http.StatusOK)
96         c.Check(resp.Body.String(), check.Matches, TestHash2+`\+[0-9]+ [0-9]+\n\n`)
97
98         // Partial index of second mount (no blocks match prefix)
99         resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks/?prefix="+TestHash[:2], tok, nil)
100         c.Check(resp.Code, check.Equals, http.StatusOK)
101         c.Check(resp.Body.String(), check.Equals, "\n")
102 }
103
104 func (s *MountsSuite) TestMetrics(c *check.C) {
105         s.call("PUT", "/"+TestHash, "", TestBlock)
106         s.call("PUT", "/"+TestHash2, "", TestBlock2)
107         resp := s.call("GET", "/metrics.json", "", nil)
108         c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
109         resp = s.call("GET", "/metrics.json", "foobar", nil)
110         c.Check(resp.Code, check.Equals, http.StatusForbidden)
111         resp = s.call("GET", "/metrics.json", arvadostest.ManagementToken, nil)
112         c.Check(resp.Code, check.Equals, http.StatusOK)
113         var j []struct {
114                 Name   string
115                 Help   string
116                 Type   string
117                 Metric []struct {
118                         Label []struct {
119                                 Name  string
120                                 Value string
121                         }
122                         Summary struct {
123                                 SampleCount string  `json:"sample_count"`
124                                 SampleSum   float64 `json:"sample_sum"`
125                                 Quantile    []struct {
126                                         Quantile float64
127                                         Value    float64
128                                 }
129                         }
130                 }
131         }
132         json.NewDecoder(resp.Body).Decode(&j)
133         found := make(map[string]bool)
134         for _, g := range j {
135                 for _, m := range g.Metric {
136                         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" {
137                                 c.Check(m.Summary.SampleCount, check.Equals, "2")
138                                 c.Check(len(m.Summary.Quantile), check.Not(check.Equals), 0)
139                                 c.Check(m.Summary.Quantile[0].Value, check.Not(check.Equals), float64(0))
140                                 found[g.Name] = true
141                         }
142                 }
143         }
144         c.Check(found["request_duration_seconds"], check.Equals, true)
145         c.Check(found["time_to_status_seconds"], check.Equals, true)
146 }
147
148 func (s *MountsSuite) call(method, path, tok string, body []byte) *httptest.ResponseRecorder {
149         resp := httptest.NewRecorder()
150         req, _ := http.NewRequest(method, path, bytes.NewReader(body))
151         if tok != "" {
152                 req.Header.Set("Authorization", "Bearer "+tok)
153         }
154         s.rtr.ServeHTTP(resp, req)
155         return resp
156 }