github.com/mre-fog/trillianxx@v1.1.2-0.20180615153820-ae375a99d36a/quota/etcd/etcdqm/etcdqm_test.go (about) 1 // Copyright 2017 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package etcdqm 16 17 import ( 18 "context" 19 "fmt" 20 "math" 21 "os" 22 "testing" 23 24 "github.com/coreos/etcd/clientv3" 25 "github.com/google/trillian/quota" 26 "github.com/google/trillian/quota/etcd/storage" 27 "github.com/google/trillian/quota/etcd/storagepb" 28 "github.com/google/trillian/testonly/integration/etcd" 29 "github.com/kylelemons/godebug/pretty" 30 ) 31 32 const ( 33 treeID = 12345 34 userID = "llama" 35 ) 36 37 var ( 38 cfgs = &storagepb.Configs{ 39 Configs: []*storagepb.Config{ 40 { 41 Name: "quotas/global/write/config", 42 State: storagepb.Config_ENABLED, 43 MaxTokens: 100, 44 ReplenishmentStrategy: &storagepb.Config_SequencingBased{ 45 SequencingBased: &storagepb.SequencingBasedStrategy{}, 46 }, 47 }, 48 { 49 Name: fmt.Sprintf("quotas/trees/%v/write/config", treeID), 50 State: storagepb.Config_ENABLED, 51 MaxTokens: 200, 52 ReplenishmentStrategy: &storagepb.Config_SequencingBased{ 53 SequencingBased: &storagepb.SequencingBasedStrategy{}, 54 }, 55 }, 56 { 57 Name: fmt.Sprintf("quotas/users/%v/read/config", userID), 58 State: storagepb.Config_ENABLED, 59 MaxTokens: 1000, 60 ReplenishmentStrategy: &storagepb.Config_TimeBased{ 61 TimeBased: &storagepb.TimeBasedStrategy{ 62 ReplenishIntervalSeconds: 100, 63 TokensToReplenish: 1000, 64 }, 65 }, 66 }, 67 }, 68 } 69 globalWriteConfig = cfgs.Configs[0] 70 treeWriteConfig = cfgs.Configs[1] 71 userReadConfig = cfgs.Configs[2] 72 73 globalWriteSpec = quota.Spec{Group: quota.Global, Kind: quota.Write} 74 treeWriteSpec = quota.Spec{Group: quota.Tree, Kind: quota.Write, TreeID: treeID} 75 userReadSpec = quota.Spec{Group: quota.User, Kind: quota.Read, User: userID} 76 77 // client is an etcd client. 78 // Initialized by TestMain(). 79 client *clientv3.Client 80 ) 81 82 func TestMain(m *testing.M) { 83 _, c, cleanup, err := etcd.StartEtcd() 84 if err != nil { 85 panic(fmt.Sprintf("StartEtcd() returned err = %v", err)) 86 } 87 client = c 88 exitCode := m.Run() 89 cleanup() 90 os.Exit(exitCode) 91 } 92 93 func TestManager_GetTokens(t *testing.T) { 94 tests := []struct { 95 desc string 96 numTokens int 97 specs []quota.Spec 98 }{ 99 { 100 desc: "singleSpec", 101 numTokens: 10, 102 specs: []quota.Spec{globalWriteSpec}, 103 }, 104 { 105 desc: "multiSpecs", 106 numTokens: 10, 107 specs: []quota.Spec{userReadSpec, treeWriteSpec, globalWriteSpec}, 108 }, 109 } 110 111 qs := &storage.QuotaStorage{Client: client} 112 qm := New(client) 113 114 ctx := context.Background() 115 for _, test := range tests { 116 if err := reset(ctx, qs, cfgs); err != nil { 117 t.Fatalf("%v: reset: %v", test.desc, err) 118 } 119 differ := newQuotaDiffer(qs, test.specs) 120 if err := differ.snapshot(ctx); err != nil { 121 t.Fatalf("%v: snapshot: %v", test.desc, err) 122 } 123 if err := qm.GetTokens(ctx, test.numTokens, test.specs); err != nil { 124 t.Errorf("%v: GetTokens() returned err = %v", test.desc, err) 125 continue 126 } 127 if err := differ.assertDiff(ctx, -test.numTokens); err != nil { 128 t.Errorf("%v: assertDiff: %v", test.desc, err) 129 } 130 } 131 } 132 133 func TestManager_GetTokensErrors(t *testing.T) { 134 tests := []struct { 135 desc string 136 numTokens int 137 specs []quota.Spec 138 }{ 139 { 140 desc: "singleSpec", 141 numTokens: int(globalWriteConfig.MaxTokens) + 1, 142 specs: []quota.Spec{globalWriteSpec}, 143 }, 144 { 145 desc: "multiSpecs", 146 numTokens: int(min(userReadConfig.MaxTokens, treeWriteConfig.MaxTokens, globalWriteConfig.MaxTokens)) + 1, 147 specs: []quota.Spec{userReadSpec, treeWriteSpec, globalWriteSpec}, 148 }, 149 } 150 151 qs := &storage.QuotaStorage{Client: client} 152 qm := New(client) 153 154 ctx := context.Background() 155 for _, test := range tests { 156 if err := reset(ctx, qs, cfgs); err != nil { 157 t.Fatalf("%v: reset: %v", test.desc, err) 158 } 159 if err := qm.GetTokens(ctx, test.numTokens, test.specs); err == nil { 160 t.Errorf("%v: GetTokens() returned err = nil, want non-nil", test.desc) 161 } 162 } 163 } 164 165 func TestManager_PeekTokens(t *testing.T) { 166 ctx := context.Background() 167 qs := &storage.QuotaStorage{Client: client} 168 if err := reset(ctx, qs, cfgs); err != nil { 169 t.Fatalf("reset() returned err = %v", err) 170 } 171 172 tests := []struct { 173 desc string 174 specs []quota.Spec 175 want map[quota.Spec]int 176 }{ 177 { 178 desc: "singleSpec", 179 specs: []quota.Spec{globalWriteSpec}, 180 want: map[quota.Spec]int{ 181 globalWriteSpec: int(globalWriteConfig.MaxTokens), 182 }, 183 }, 184 { 185 desc: "multiSpecs", 186 specs: []quota.Spec{userReadSpec, treeWriteSpec, globalWriteSpec}, 187 want: map[quota.Spec]int{ 188 globalWriteSpec: int(globalWriteConfig.MaxTokens), 189 treeWriteSpec: int(treeWriteConfig.MaxTokens), 190 userReadSpec: int(userReadConfig.MaxTokens), 191 }, 192 }, 193 } 194 195 qm := New(client) 196 for _, test := range tests { 197 tokens, err := qm.PeekTokens(ctx, test.specs) 198 if err != nil { 199 t.Errorf("%v: PeekTokens() returned err = %v", test.desc, err) 200 continue 201 } 202 if diff := pretty.Compare(tokens, test.want); diff != "" { 203 t.Errorf("%v: post-PeekTokens() diff (-got +want):\n%v", test.desc, diff) 204 } 205 } 206 } 207 208 func TestManager_PutTokens(t *testing.T) { 209 ctx := context.Background() 210 qs := &storage.QuotaStorage{Client: client} 211 if err := drain(ctx, qs, cfgs); err != nil { 212 t.Fatalf("drain() returned err = %v", err) 213 } 214 215 tests := []struct { 216 desc string 217 numTokens int 218 specs []quota.Spec 219 }{ 220 { 221 desc: "singleSpec", 222 numTokens: 10, 223 specs: []quota.Spec{globalWriteSpec}, 224 }, 225 { 226 desc: "multiSpecs", 227 numTokens: 11, 228 specs: []quota.Spec{globalWriteSpec, treeWriteSpec}, 229 }, 230 } 231 232 qm := New(client) 233 for _, test := range tests { 234 differ := newQuotaDiffer(qs, test.specs) 235 if err := differ.snapshot(ctx); err != nil { 236 t.Fatalf("%v: snapshot: %v", test.desc, err) 237 } 238 if err := qm.PutTokens(ctx, test.numTokens, test.specs); err != nil { 239 t.Errorf("%v: PutTokens() returned err = %v", test.desc, err) 240 continue 241 } 242 if err := differ.assertDiff(ctx, test.numTokens); err != nil { 243 t.Errorf("%v: assertDiff: %v", test.desc, err) 244 } 245 } 246 } 247 248 func TestManager_ResetQuota(t *testing.T) { 249 tests := []struct { 250 desc string 251 specs []quota.Spec 252 want map[quota.Spec]int 253 }{ 254 { 255 desc: "singleSpec", 256 specs: []quota.Spec{globalWriteSpec}, 257 want: map[quota.Spec]int{ 258 globalWriteSpec: int(globalWriteConfig.MaxTokens), 259 }, 260 }, 261 { 262 desc: "multiSpecs", 263 specs: []quota.Spec{globalWriteSpec, treeWriteSpec, userReadSpec}, 264 want: map[quota.Spec]int{ 265 globalWriteSpec: int(globalWriteConfig.MaxTokens), 266 treeWriteSpec: int(treeWriteConfig.MaxTokens), 267 userReadSpec: int(userReadConfig.MaxTokens), 268 }, 269 }, 270 } 271 272 qs := &storage.QuotaStorage{Client: client} 273 qm := New(client) 274 275 ctx := context.Background() 276 for _, test := range tests { 277 if err := drain(ctx, qs, cfgs); err != nil { 278 t.Fatalf("%v: drain() returned err = %v", test.desc, err) 279 } 280 if err := qm.ResetQuota(ctx, test.specs); err != nil { 281 t.Errorf("%v: ResetQuota() returned err = %v", test.desc, err) 282 continue 283 } 284 tokens, err := qm.PeekTokens(ctx, test.specs) 285 if err != nil { 286 t.Fatalf("%v: PeekTokens() returned err = %v", test.desc, err) 287 } 288 if diff := pretty.Compare(tokens, test.want); diff != "" { 289 t.Errorf("%v: post-PeekTokens() diff (-got +want):\n%v", test.desc, diff) 290 } 291 } 292 } 293 294 func TestConfigName(t *testing.T) { 295 tests := []struct { 296 spec quota.Spec 297 want string 298 }{ 299 { 300 spec: quota.Spec{Group: quota.Global, Kind: quota.Read}, 301 want: "quotas/global/read/config", 302 }, 303 { 304 spec: quota.Spec{Group: quota.Global, Kind: quota.Write}, 305 want: "quotas/global/write/config", 306 }, 307 { 308 spec: quota.Spec{Group: quota.Tree, Kind: quota.Read, TreeID: 10}, 309 want: "quotas/trees/10/read/config", 310 }, 311 { 312 spec: quota.Spec{Group: quota.Tree, Kind: quota.Write, TreeID: 11}, 313 want: "quotas/trees/11/write/config", 314 }, 315 { 316 spec: quota.Spec{Group: quota.User, Kind: quota.Read, User: "alpaca"}, 317 want: "quotas/users/alpaca/read/config", 318 }, 319 { 320 spec: quota.Spec{Group: quota.User, Kind: quota.Write, User: "llama"}, 321 want: "quotas/users/llama/write/config", 322 }, 323 } 324 for _, test := range tests { 325 if got := configName(test.spec); got != test.want { 326 t.Errorf("configName(%+v) = %v, want = %v", test.spec, got, test.want) 327 } 328 } 329 } 330 331 func min(nums ...int64) int64 { 332 ret := int64(math.MaxInt64) 333 for _, n := range nums { 334 if n < ret { 335 ret = n 336 } 337 } 338 return ret 339 } 340 341 // drain applies cfgs (as per reset()) and consumes all tokens from it. 342 func drain(ctx context.Context, qs *storage.QuotaStorage, cfgs *storagepb.Configs) error { 343 if err := reset(ctx, qs, cfgs); err != nil { 344 return err 345 } 346 for _, cfg := range cfgs.Configs { 347 if err := qs.Get(ctx, []string{cfg.Name}, cfg.MaxTokens); err != nil { 348 return fmt.Errorf("%v: %v", cfg.Name, err) 349 } 350 } 351 return nil 352 } 353 354 func reset(ctx context.Context, qs *storage.QuotaStorage, cfgs *storagepb.Configs) error { 355 if _, err := qs.UpdateConfigs(ctx, true /* reset */, func(c *storagepb.Configs) { *c = *cfgs }); err != nil { 356 return fmt.Errorf("UpdateConfigs() returned err = %v", err) 357 } 358 return nil 359 } 360 361 type quotaDiffer struct { 362 qs *storage.QuotaStorage 363 names []string 364 tokens map[string]int64 365 } 366 367 func newQuotaDiffer(qs *storage.QuotaStorage, specs []quota.Spec) *quotaDiffer { 368 return "aDiffer{qs: qs, names: configNames(specs)} 369 } 370 371 func (d *quotaDiffer) snapshot(ctx context.Context) error { 372 var err error 373 d.tokens, err = d.qs.Peek(ctx, d.names) 374 return err 375 } 376 377 func (d *quotaDiffer) assertDiff(ctx context.Context, want int) error { 378 currentTokens, err := d.qs.Peek(ctx, d.names) 379 if err != nil { 380 return fmt.Errorf("Peek() returned err = %v", err) 381 } 382 want64 := int64(want) 383 for k, v := range currentTokens { 384 if got := v - d.tokens[k]; got != want64 { 385 return fmt.Errorf("%v has a diff of %v, want = %v", k, got, want64) 386 } 387 } 388 return nil 389 }