Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(382)

Side by Side Diff: server/logdog/storage/bigtable/storage_test.go

Issue 1872903002: LogDog: Enable keys-only BigTable queries. (Closed) Base URL: https://github.com/luci/luci-go@logdog-archive-v2
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package bigtable 5 package bigtable
6 6
7 import ( 7 import (
8 "bytes" 8 "bytes"
9 » "errors" 9 » "fmt"
10 "testing" 10 "testing"
11 "time" 11 "time"
12 12
13 "github.com/luci/gkvlite" 13 "github.com/luci/gkvlite"
14 "github.com/luci/luci-go/common/logdog/types" 14 "github.com/luci/luci-go/common/logdog/types"
15 "github.com/luci/luci-go/common/recordio" 15 "github.com/luci/luci-go/common/recordio"
16 "github.com/luci/luci-go/server/logdog/storage" 16 "github.com/luci/luci-go/server/logdog/storage"
17 "golang.org/x/net/context" 17 "golang.org/x/net/context"
18 "google.golang.org/cloud/bigtable"
19 18
20 . "github.com/luci/luci-go/common/testing/assertions" 19 . "github.com/luci/luci-go/common/testing/assertions"
21 . "github.com/smartystreets/goconvey/convey" 20 . "github.com/smartystreets/goconvey/convey"
22 ) 21 )
23 22
24 // btTableTest is an in-memory implementation of btTable interface for testing. 23 // btTableTest is an in-memory implementation of btTable interface for testing.
25 // 24 //
26 // This is a simple implementation; not an efficient one. 25 // This is a simple implementation; not an efficient one.
27 type btTableTest struct { 26 type btTableTest struct {
28 s *gkvlite.Store 27 s *gkvlite.Store
29 c *gkvlite.Collection 28 c *gkvlite.Collection
30 29
31 // err, if true, is the error immediately returned by functions. 30 // err, if true, is the error immediately returned by functions.
32 err error 31 err error
33 32
34 // maxLogAge is the currently-configured maximum log age. 33 // maxLogAge is the currently-configured maximum log age.
35 maxLogAge time.Duration 34 maxLogAge time.Duration
35
36 // countLegacy, if true, simulates legacy BigTable rows by not writing o r
37 // reading the "count" field.
38 countLegacy bool
36 } 39 }
37 40
38 func (t *btTableTest) close() { 41 func (t *btTableTest) close() {
39 if t.s != nil { 42 if t.s != nil {
40 t.s.Close() 43 t.s.Close()
41 t.s = nil 44 t.s = nil
42 } 45 }
43 } 46 }
44 47
45 func (t *btTableTest) collection() *gkvlite.Collection { 48 func (t *btTableTest) collection() *gkvlite.Collection {
46 if t.s == nil { 49 if t.s == nil {
47 var err error 50 var err error
48 t.s, err = gkvlite.NewStore(nil) 51 t.s, err = gkvlite.NewStore(nil)
49 if err != nil { 52 if err != nil {
50 panic(err) 53 panic(err)
51 } 54 }
52 t.c = t.s.MakePrivateCollection(bytes.Compare) 55 t.c = t.s.MakePrivateCollection(bytes.Compare)
53 } 56 }
54 return t.c 57 return t.c
55 } 58 }
56 59
57 func (t *btTableTest) putLogData(c context.Context, rk *rowKey, d []byte) error { 60 func (t *btTableTest) putLogData(c context.Context, rk *rowKey, d []byte) error {
58 if t.err != nil { 61 if t.err != nil {
59 return t.err 62 return t.err
60 } 63 }
61 64
65 // Record/count sanity check.
66 records, err := recordio.Split(d)
67 if err != nil {
68 return err
69 }
70 if int64(len(records)) != rk.count {
71 return fmt.Errorf("count mismatch (%d != %d)", len(records), rk. count)
72 }
73
62 enc := []byte(rk.encode()) 74 enc := []byte(rk.encode())
63 coll := t.collection() 75 coll := t.collection()
64 if item, _ := coll.Get(enc); item != nil { 76 if item, _ := coll.Get(enc); item != nil {
65 return storage.ErrExists 77 return storage.ErrExists
66 } 78 }
67 79
68 clone := make([]byte, len(d)) 80 clone := make([]byte, len(d))
69 copy(clone, d) 81 copy(clone, d)
70 if err := coll.Set(enc, clone); err != nil { 82 if err := coll.Set(enc, clone); err != nil {
71 panic(err) 83 panic(err)
(...skipping 12 matching lines...) Expand all
84 err := t.collection().VisitItemsAscend(enc, !keysOnly, func(i *gkvlite.I tem) bool { 96 err := t.collection().VisitItemsAscend(enc, !keysOnly, func(i *gkvlite.I tem) bool {
85 var drk *rowKey 97 var drk *rowKey
86 drk, ierr = decodeRowKey(string(i.Key)) 98 drk, ierr = decodeRowKey(string(i.Key))
87 if ierr != nil { 99 if ierr != nil {
88 return false 100 return false
89 } 101 }
90 if drk.pathPrefix() != prefix { 102 if drk.pathPrefix() != prefix {
91 return false 103 return false
92 } 104 }
93 105
94 » » if ierr = cb(drk, i.Val); ierr != nil { 106 » » // If we're simulating legacy (no count), clear our row key's co unt.
107 » » if t.countLegacy {
108 » » » drk.count = 0
109 » » }
110
111 » » rowData := i.Val
112 » » if keysOnly {
113 » » » rowData = nil
114 » » }
115
116 » » if ierr = cb(drk, rowData); ierr != nil {
95 if ierr == errStop { 117 if ierr == errStop {
96 ierr = nil 118 ierr = nil
97 } 119 }
98 return false 120 return false
99 } 121 }
100 122
101 if limit > 0 { 123 if limit > 0 {
102 limit-- 124 limit--
103 if limit == 0 { 125 if limit == 0 {
104 return false 126 return false
(...skipping 22 matching lines...) Expand all
127 err := t.collection().VisitItemsAscend([]byte(nil), true, func(i *gkvlit e.Item) bool { 149 err := t.collection().VisitItemsAscend([]byte(nil), true, func(i *gkvlit e.Item) bool {
128 result[string(i.Key)] = i.Val 150 result[string(i.Key)] = i.Val
129 return true 151 return true
130 }) 152 })
131 if err != nil { 153 if err != nil {
132 panic(err) 154 panic(err)
133 } 155 }
134 return result 156 return result
135 } 157 }
136 158
137 func TestStorage(t *testing.T) { 159 func testStorageImpl(t *testing.T, legacy bool) {
138 t.Parallel() 160 t.Parallel()
139 161
140 Convey(`A BigTable storage instance bound to a testing BigTable instance `, t, func() { 162 Convey(`A BigTable storage instance bound to a testing BigTable instance `, t, func() {
141 » » bt := btTableTest{} 163 » » bt := btTableTest{
164 » » » countLegacy: legacy,
165 » » }
142 defer bt.close() 166 defer bt.close()
143 167
144 s := newBTStorage(context.Background(), Options{ 168 s := newBTStorage(context.Background(), Options{
145 Project: "test-project", 169 Project: "test-project",
146 Zone: "test-zone", 170 Zone: "test-zone",
147 Cluster: "test-cluster", 171 Cluster: "test-cluster",
148 LogTable: "test-log-table", 172 LogTable: "test-log-table",
149 }, nil, nil) 173 }, nil, nil)
150 174
151 s.raw = &bt 175 s.raw = &bt
(...skipping 19 matching lines...) Expand all
171 data[i] = []byte(v) 195 data[i] = []byte(v)
172 } 196 }
173 197
174 return s.Put(storage.PutRequest{ 198 return s.Put(storage.PutRequest{
175 Path: types.StreamPath(path), 199 Path: types.StreamPath(path),
176 Index: types.MessageIndex(index), 200 Index: types.MessageIndex(index),
177 Values: data, 201 Values: data,
178 }) 202 })
179 } 203 }
180 204
181 » » ekey := func(p string, v int64) string { 205 » » ekey := func(p string, v, c int64) string {
182 » » » return newRowKey(p, v).encode() 206 » » » return newRowKey(p, v, c).encode()
183 } 207 }
184 records := func(s ...string) []byte { 208 records := func(s ...string) []byte {
185 buf := bytes.Buffer{} 209 buf := bytes.Buffer{}
186 w := recordio.NewWriter(&buf) 210 w := recordio.NewWriter(&buf)
187 211
188 for _, v := range s { 212 for _, v := range s {
189 if _, err := w.Write([]byte(v)); err != nil { 213 if _, err := w.Write([]byte(v)); err != nil {
190 panic(err) 214 panic(err)
191 } 215 }
192 if err := w.Flush(); err != nil { 216 if err := w.Flush(); err != nil {
193 panic(err) 217 panic(err)
194 } 218 }
195 } 219 }
196 220
197 return buf.Bytes() 221 return buf.Bytes()
198 } 222 }
199 223
200 Convey(`With an artificial maximum BigTable row size of two reco rds`, func() { 224 Convey(`With an artificial maximum BigTable row size of two reco rds`, func() {
201 // Artificially constrain row size. 4 = 2*{size/1, data/ 1} RecordIO 225 // Artificially constrain row size. 4 = 2*{size/1, data/ 1} RecordIO
202 // entries. 226 // entries.
203 s.maxRowSize = 4 227 s.maxRowSize = 4
204 228
205 Convey(`Will split row data that overflows the table int o multiple rows.`, func() { 229 Convey(`Will split row data that overflows the table int o multiple rows.`, func() {
206 So(put("A", 0, "0", "1", "2", "3"), ShouldBeNil) 230 So(put("A", 0, "0", "1", "2", "3"), ShouldBeNil)
207 231
208 So(bt.dataMap(), ShouldResemble, map[string][]by te{ 232 So(bt.dataMap(), ShouldResemble, map[string][]by te{
209 » » » » » ekey("A", 1): records("0", "1"), 233 » » » » » ekey("A", 1, 2): records("0", "1"),
210 » » » » » ekey("A", 3): records("2", "3"), 234 » » » » » ekey("A", 3, 2): records("2", "3"),
211 }) 235 })
212 }) 236 })
213 237
214 Convey(`Loading a single row data beyond the maximum row size will fail.`, func() { 238 Convey(`Loading a single row data beyond the maximum row size will fail.`, func() {
215 So(put("A", 0, "0123"), ShouldErrLike, "single r ow entry exceeds maximum size") 239 So(put("A", 0, "0123"), ShouldErrLike, "single r ow entry exceeds maximum size")
216 }) 240 })
217 }) 241 })
218 242
219 Convey(`With row data: A{0, 1, 2, 3, 4}, B{10, 12, 13}`, func() { 243 Convey(`With row data: A{0, 1, 2, 3, 4}, B{10, 12, 13}`, func() {
220 So(put("A", 0, "0", "1", "2"), ShouldBeNil) 244 So(put("A", 0, "0", "1", "2"), ShouldBeNil)
221 So(put("A", 3, "3", "4"), ShouldBeNil) 245 So(put("A", 3, "3", "4"), ShouldBeNil)
222 So(put("B", 10, "10"), ShouldBeNil) 246 So(put("B", 10, "10"), ShouldBeNil)
223 So(put("B", 12, "12", "13"), ShouldBeNil) 247 So(put("B", 12, "12", "13"), ShouldBeNil)
224 248
225 Convey(`Testing "Put"...`, func() { 249 Convey(`Testing "Put"...`, func() {
226 Convey(`Loads the row data.`, func() { 250 Convey(`Loads the row data.`, func() {
227 So(bt.dataMap(), ShouldResemble, map[str ing][]byte{ 251 So(bt.dataMap(), ShouldResemble, map[str ing][]byte{
228 » » » » » » ekey("A", 2): records("0", "1", "2"), 252 » » » » » » ekey("A", 2, 3): records("0", " 1", "2"),
229 » » » » » » ekey("A", 4): records("3", "4") , 253 » » » » » » ekey("A", 4, 2): records("3", " 4"),
230 » » » » » » ekey("B", 10): records("10"), 254 » » » » » » ekey("B", 10, 1): records("10"),
231 » » » » » » ekey("B", 13): records("12", "13 "), 255 » » » » » » ekey("B", 13, 2): records("12", "13"),
232 }) 256 })
233 }) 257 })
234 }) 258 })
235 259
236 Convey(`Testing "Get"...`, func() { 260 Convey(`Testing "Get"...`, func() {
237 Convey(`Can fetch the full row, "A".`, func() { 261 Convey(`Can fetch the full row, "A".`, func() {
238 got, err := get("A", 0, 0) 262 got, err := get("A", 0, 0)
239 So(err, ShouldBeNil) 263 So(err, ShouldBeNil)
240 So(got, ShouldResemble, []string{"0", "1 ", "2", "3", "4"}) 264 So(got, ShouldResemble, []string{"0", "1 ", "2", "3", "4"})
241 }) 265 })
242 266
243 » » » » Convey(`Will fetch A{1, 2, 3, 4} with when index =1.`, func() { 267 » » » » Convey(`Will fetch A{1, 2, 3, 4} with index=1.`, func() {
244 got, err := get("A", 1, 0) 268 got, err := get("A", 1, 0)
245 So(err, ShouldBeNil) 269 So(err, ShouldBeNil)
246 So(got, ShouldResemble, []string{"1", "2 ", "3", "4"}) 270 So(got, ShouldResemble, []string{"1", "2 ", "3", "4"})
247 }) 271 })
248 272
249 » » » » Convey(`Will fetch A{1, 2} with when index=1 and limit=2.`, func() { 273 » » » » Convey(`Will fetch A{1, 2} with index=1 and limi t=2.`, func() {
250 got, err := get("A", 1, 2) 274 got, err := get("A", 1, 2)
251 So(err, ShouldBeNil) 275 So(err, ShouldBeNil)
252 So(got, ShouldResemble, []string{"1", "2 "}) 276 So(got, ShouldResemble, []string{"1", "2 "})
253 }) 277 })
254 278
255 Convey(`Will fetch B{10, 12, 13} for B.`, func() { 279 Convey(`Will fetch B{10, 12, 13} for B.`, func() {
256 got, err := get("B", 0, 0) 280 got, err := get("B", 0, 0)
257 So(err, ShouldBeNil) 281 So(err, ShouldBeNil)
258 So(got, ShouldResemble, []string{"10", " 12", "13"}) 282 So(got, ShouldResemble, []string{"10", " 12", "13"})
259 }) 283 })
(...skipping 28 matching lines...) Expand all
288 So(err, ShouldBeNil) 312 So(err, ShouldBeNil)
289 So(got, ShouldEqual, "13") 313 So(got, ShouldEqual, "13")
290 }) 314 })
291 315
292 Convey(`A tail request for "INVALID" errors NOT FOUND.`, func() { 316 Convey(`A tail request for "INVALID" errors NOT FOUND.`, func() {
293 _, err := tail("INVALID") 317 _, err := tail("INVALID")
294 So(err, ShouldEqual, storage.ErrDoesNotE xist) 318 So(err, ShouldEqual, storage.ErrDoesNotE xist)
295 }) 319 })
296 }) 320 })
297 }) 321 })
298
299 Convey(`Given a fake BigTable row`, func() {
300 fakeRow := bigtable.Row{
301 "log": []bigtable.ReadItem{
302 {
303 Row: "testrow",
304 Column: "log:data",
305 Value: []byte("here is my data" ),
306 },
307 },
308 }
309
310 Convey(`Can extract log data.`, func() {
311 d, err := getLogData(fakeRow)
312 So(err, ShouldBeNil)
313 So(d, ShouldResemble, []byte("here is my data"))
314 })
315
316 Convey(`Will fail to extract if the column is missing.`, func() {
317 fakeRow["log"][0].Column = "not-data"
318 _, err := getLogData(fakeRow)
319 So(err, ShouldEqual, storage.ErrDoesNotExist)
320 })
321
322 Convey(`Will fail to extract if the family does not exis t.`, func() {
323 So(getReadItem(fakeRow, "invalid", "invalid"), S houldBeNil)
324 })
325
326 Convey(`Will fail to extract if the column does not exis t.`, func() {
327 So(getReadItem(fakeRow, "log", "invalid"), Shoul dBeNil)
328 })
329 })
330
331 Convey(`When pushing a configuration`, func() {
332 cfg := storage.Config{
333 MaxLogAge: 1 * time.Hour,
334 }
335
336 Convey(`Can successfully apply configuration.`, func() {
337 So(s.Config(cfg), ShouldBeNil)
338 So(bt.maxLogAge, ShouldEqual, cfg.MaxLogAge)
339 })
340
341 Convey(`With return an error if the configuration fails to apply.`, func() {
342 bt.err = errors.New("test error")
343
344 So(s.Config(cfg), ShouldEqual, bt.err)
345 })
346 })
347 }) 322 })
348 } 323 }
324
325 func TestStorage(t *testing.T) {
326 testStorageImpl(t, false)
327 }
328
329 func TestStorageLegacy(t *testing.T) {
330 testStorageImpl(t, true)
331 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698