github.com/zorawar87/trillian@v1.2.1/quota/etcd/quotaapi/quota_server_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 quotaapi 16 17 import ( 18 "context" 19 "fmt" 20 "math/rand" 21 "net" 22 "os" 23 "sort" 24 "strings" 25 "sync" 26 "testing" 27 28 "github.com/coreos/etcd/clientv3" 29 "github.com/golang/protobuf/proto" 30 "github.com/google/trillian/quota" 31 "github.com/google/trillian/quota/etcd/quotapb" 32 "github.com/google/trillian/quota/etcd/storage" 33 "github.com/google/trillian/quota/etcd/storagepb" 34 "github.com/google/trillian/server/interceptor" 35 "github.com/google/trillian/testonly/integration/etcd" 36 "github.com/kylelemons/godebug/pretty" 37 "google.golang.org/genproto/protobuf/field_mask" 38 "google.golang.org/grpc" 39 "google.golang.org/grpc/codes" 40 "google.golang.org/grpc/status" 41 ) 42 43 var ( 44 globalWrite = "apb.Config{ 45 Name: "quotas/global/write/config", 46 State: quotapb.Config_ENABLED, 47 MaxTokens: 100, 48 CurrentTokens: 100, // Assume quota is full 49 ReplenishmentStrategy: "apb.Config_SequencingBased{ 50 SequencingBased: "apb.SequencingBasedStrategy{}, 51 }, 52 } 53 globalRead = "apb.Config{ 54 Name: "quotas/global/read/config", 55 State: quotapb.Config_ENABLED, 56 MaxTokens: 100, 57 CurrentTokens: 100, // Assume quota is full 58 ReplenishmentStrategy: "apb.Config_TimeBased{ 59 TimeBased: "apb.TimeBasedStrategy{ 60 TokensToReplenish: 10000, 61 ReplenishIntervalSeconds: 100, 62 }, 63 }, 64 } 65 tree1Read = copyWithName(globalRead, "quotas/trees/1/read/config") 66 tree1Write = copyWithName(globalWrite, "quotas/trees/1/write/config") 67 tree2Read = copyWithName(globalRead, "quotas/trees/2/read/config") 68 tree2Write = copyWithName(globalWrite, "quotas/trees/2/write/config") 69 userRead = copyWithName(globalRead, "quotas/users/llama/read/config") 70 userWrite = copyWithName(globalRead, "quotas/users/llama/write/config") 71 72 etcdClient *clientv3.Client 73 quotaClient quotapb.QuotaClient 74 ) 75 76 func copyWithName(c *quotapb.Config, name string) *quotapb.Config { 77 cp := *c 78 cp.Name = name 79 return &cp 80 } 81 82 func TestMain(m *testing.M) { 83 _, ec, stopEtcd, err := etcd.StartEtcd() 84 if err != nil { 85 panic(fmt.Sprintf("StartEtcd() returned err = %v", err)) 86 } 87 // Don't defer stopEtcd(): the os.Exit() call below doesn't allow for defers. 88 etcdClient = ec 89 90 qc, stopServer, err := startServer(ec) 91 if err != nil { 92 stopEtcd() 93 panic(fmt.Sprintf("startServer() returned err = %v", err)) 94 } 95 // Don't defer stopServer(). 96 quotaClient = qc 97 98 exitCode := m.Run() 99 stopServer() 100 stopEtcd() 101 os.Exit(exitCode) 102 } 103 104 func TestServer_CreateConfig(t *testing.T) { 105 globalWrite2 := *globalWrite 106 globalWrite2.MaxTokens += 10 107 globalWrite2.CurrentTokens = globalWrite2.MaxTokens 108 109 overrideName := *globalWrite 110 overrideName.Name = "ignored" 111 112 invalidConfig := *globalWrite 113 invalidConfig.State = quotapb.Config_UNKNOWN_CONFIG_STATE 114 115 tests := []upsertTest{ 116 { 117 desc: "emptyName", 118 req: "apb.CreateConfigRequest{Config: globalWrite}, 119 wantCode: codes.InvalidArgument, 120 }, 121 { 122 desc: "emptyConfig", 123 req: "apb.CreateConfigRequest{Name: globalWrite.Name}, 124 wantCode: codes.InvalidArgument, 125 }, 126 { 127 desc: "success", 128 req: "apb.CreateConfigRequest{Name: globalWrite.Name, Config: globalWrite}, 129 wantCfg: globalWrite, 130 }, 131 { 132 desc: "alreadyExists", 133 baseCfg: globalWrite, 134 req: "apb.CreateConfigRequest{Name: globalWrite.Name, Config: &globalWrite2}, 135 wantCode: codes.AlreadyExists, 136 wantCfg: globalWrite, 137 }, 138 { 139 desc: "overrideName", 140 req: "apb.CreateConfigRequest{ 141 Name: globalWrite.Name, 142 Config: &overrideName, 143 }, 144 wantCfg: globalWrite, 145 }, 146 { 147 desc: "invalidConfig", 148 req: "apb.CreateConfigRequest{ 149 Name: "quotas/global/read/config", 150 Config: &invalidConfig, 151 }, 152 wantCode: codes.InvalidArgument, 153 }, 154 } 155 156 ctx := context.Background() 157 rpc := func(ctx context.Context, req interface{}) (*quotapb.Config, error) { 158 return quotaClient.CreateConfig(ctx, req.(*quotapb.CreateConfigRequest)) 159 } 160 for _, test := range tests { 161 if err := runUpsertTest(ctx, test, rpc, "CreateConfig"); err != nil { 162 t.Error(err) 163 } 164 } 165 } 166 167 func TestServer_UpdateConfig(t *testing.T) { 168 disabledGlobalWrite := *globalWrite 169 // Disabled quotas have "infinite" tokens 170 disabledGlobalWrite.CurrentTokens = int64(quota.MaxTokens) 171 disabledGlobalWrite.State = quotapb.Config_DISABLED 172 173 timeBasedGlobalWrite := *globalWrite 174 timeBasedGlobalWrite.MaxTokens += 100 175 timeBasedGlobalWrite.CurrentTokens = globalWrite.MaxTokens 176 timeBasedGlobalWrite.ReplenishmentStrategy = "apb.Config_TimeBased{ 177 TimeBased: "apb.TimeBasedStrategy{ 178 TokensToReplenish: 100, 179 ReplenishIntervalSeconds: 200, 180 }, 181 } 182 183 timeBasedGlobalWrite2 := timeBasedGlobalWrite 184 timeBasedGlobalWrite2.CurrentTokens = timeBasedGlobalWrite.MaxTokens 185 timeBasedGlobalWrite2.GetTimeBased().TokensToReplenish += 50 186 timeBasedGlobalWrite2.GetTimeBased().ReplenishIntervalSeconds -= 20 187 188 tests := []upsertTest{ 189 { 190 desc: "disableQuota", 191 baseCfg: globalWrite, 192 req: "apb.UpdateConfigRequest{ 193 Name: globalWrite.Name, 194 Config: "apb.Config{State: disabledGlobalWrite.State}, 195 UpdateMask: &field_mask.FieldMask{Paths: []string{"state"}}, 196 }, 197 wantCfg: &disabledGlobalWrite, 198 }, 199 { 200 desc: "timeBasedGlobalWrite", 201 baseCfg: globalWrite, 202 req: "apb.UpdateConfigRequest{ 203 Name: globalWrite.Name, 204 Config: "apb.Config{ 205 MaxTokens: timeBasedGlobalWrite.MaxTokens, 206 ReplenishmentStrategy: timeBasedGlobalWrite.ReplenishmentStrategy, 207 }, 208 UpdateMask: &field_mask.FieldMask{Paths: []string{"max_tokens", "time_based"}}, 209 }, 210 wantCfg: &timeBasedGlobalWrite, 211 }, 212 { 213 desc: "timeBasedGlobalWrite2", 214 baseCfg: &timeBasedGlobalWrite, 215 req: "apb.UpdateConfigRequest{ 216 Name: timeBasedGlobalWrite.Name, 217 Config: "apb.Config{ 218 ReplenishmentStrategy: timeBasedGlobalWrite2.ReplenishmentStrategy, 219 }, 220 UpdateMask: &field_mask.FieldMask{Paths: []string{"time_based"}}, 221 }, 222 wantCfg: &timeBasedGlobalWrite2, 223 }, 224 { 225 desc: "unknownConfig", 226 req: "apb.UpdateConfigRequest{ 227 Name: globalWrite.Name, 228 Config: "apb.Config{MaxTokens: 200}, 229 UpdateMask: &field_mask.FieldMask{Paths: []string{"max_tokens"}}, 230 }, 231 wantCode: codes.NotFound, 232 }, 233 { 234 desc: "badConfig", 235 baseCfg: globalWrite, 236 req: "apb.UpdateConfigRequest{ 237 Name: globalWrite.Name, 238 Config: "apb.Config{}, // State == UNKNOWN 239 UpdateMask: &field_mask.FieldMask{Paths: []string{"state"}}, 240 }, 241 wantCode: codes.InvalidArgument, 242 wantCfg: globalWrite, 243 }, 244 { 245 desc: "badPath", 246 baseCfg: globalWrite, 247 req: "apb.UpdateConfigRequest{ 248 Name: globalWrite.Name, 249 Config: "apb.Config{}, 250 UpdateMask: &field_mask.FieldMask{Paths: []string{"NOT_A_FIELD"}}, 251 }, 252 wantCode: codes.InvalidArgument, 253 wantCfg: globalWrite, 254 }, 255 { 256 desc: "emptyName", 257 req: "apb.UpdateConfigRequest{ 258 Config: "apb.Config{MaxTokens: 100}, 259 UpdateMask: &field_mask.FieldMask{Paths: []string{"max_tokens"}}, 260 }, 261 wantCode: codes.InvalidArgument, 262 }, 263 { 264 desc: "emptyConfig", 265 baseCfg: globalWrite, 266 req: "apb.UpdateConfigRequest{ 267 Name: globalWrite.Name, 268 UpdateMask: &field_mask.FieldMask{Paths: []string{"max_tokens"}}, 269 }, 270 wantCode: codes.InvalidArgument, 271 wantCfg: globalWrite, 272 }, 273 { 274 desc: "emptyMask", 275 baseCfg: globalWrite, 276 req: "apb.UpdateConfigRequest{ 277 Name: globalWrite.Name, 278 Config: "apb.Config{MaxTokens: 100}, 279 }, 280 wantCode: codes.InvalidArgument, 281 wantCfg: globalWrite, 282 }, 283 { 284 desc: "emptyConfigWithReset", 285 baseCfg: globalWrite, 286 req: "apb.UpdateConfigRequest{ 287 Name: globalWrite.Name, 288 UpdateMask: &field_mask.FieldMask{Paths: []string{"max_tokens"}}, 289 ResetQuota: true, 290 }, 291 wantCode: codes.InvalidArgument, 292 wantCfg: globalWrite, 293 }, 294 { 295 desc: "emptyMaskWithReset", 296 baseCfg: globalWrite, 297 req: "apb.UpdateConfigRequest{ 298 Name: globalWrite.Name, 299 Config: "apb.Config{MaxTokens: 100}, 300 ResetQuota: true, 301 }, 302 wantCode: codes.InvalidArgument, 303 wantCfg: globalWrite, 304 }, 305 } 306 307 ctx := context.Background() 308 rpc := func(ctx context.Context, req interface{}) (*quotapb.Config, error) { 309 return quotaClient.UpdateConfig(ctx, req.(*quotapb.UpdateConfigRequest)) 310 } 311 for _, test := range tests { 312 if err := runUpsertTest(ctx, test, rpc, "UpdateConfig"); err != nil { 313 t.Error(err) 314 } 315 } 316 } 317 318 func TestServer_UpdateConfig_ResetQuota(t *testing.T) { 319 ctx := context.Background() 320 if err := reset(ctx); err != nil { 321 t.Fatalf("reset() returned err = %v", err) 322 } 323 if _, err := quotaClient.CreateConfig(ctx, "apb.CreateConfigRequest{ 324 Name: globalWrite.Name, 325 Config: globalWrite, 326 }); err != nil { 327 t.Fatalf("CreateConfig() returned err = %v", err) 328 } 329 qs := storage.QuotaStorage{Client: etcdClient} 330 if err := qs.Get(ctx, []string{globalWrite.Name}, globalWrite.MaxTokens-1); err != nil { 331 t.Fatalf("Get() returned err = %v", err) 332 } 333 334 cfg := *globalWrite 335 cfg.MaxTokens += 10 336 337 tests := []struct { 338 desc string 339 req *quotapb.UpdateConfigRequest 340 wantTokens int64 341 }{ 342 { 343 desc: "resetFalse", 344 req: "apb.UpdateConfigRequest{ 345 Name: globalWrite.Name, 346 Config: &cfg, 347 UpdateMask: &field_mask.FieldMask{Paths: []string{"max_tokens"}}, 348 }, 349 wantTokens: 1, 350 }, 351 { 352 desc: "resetTrue", 353 req: "apb.UpdateConfigRequest{Name: globalWrite.Name, ResetQuota: true}, 354 wantTokens: cfg.MaxTokens, 355 }, 356 } 357 for _, test := range tests { 358 if _, err := quotaClient.UpdateConfig(ctx, test.req); err != nil { 359 t.Errorf("%v: UpdateConfig() returned err = %v", test.desc, err) 360 continue 361 } 362 363 tokens, err := qs.Peek(ctx, []string{test.req.Name}) 364 if err != nil { 365 t.Fatalf("%v: Peek() returned err = %v", test.desc, err) 366 } 367 if got := tokens[test.req.Name]; got != test.wantTokens { 368 t.Errorf("%v: %q has %v tokens, want = %v", test.desc, test.req.Name, got, test.wantTokens) 369 } 370 } 371 } 372 373 func TestServer_UpdateConfig_Race(t *testing.T) { 374 if testing.Short() { 375 t.Skip("Skipping race test due to short mode") 376 return 377 } 378 379 ctx := context.Background() 380 if err := reset(ctx); err != nil { 381 t.Fatalf("reset() returned err = %v", err) 382 } 383 384 // Quotas we'll use for the test, a few of each type 385 configs := []*quotapb.Config{globalRead, globalWrite, tree1Read, tree1Write, userRead, userWrite} 386 387 // Create the configs we'll use for the test concurrently. 388 // If we get any errors a msg is sent to createErrs; that's a sign to stop 389 // the test *after* all spawned goroutines have exited. 390 createErrs := make(chan bool, len(configs)) 391 wg := sync.WaitGroup{} 392 for _, cfg := range configs { 393 cfg := cfg 394 wg.Add(1) 395 go func() { 396 defer wg.Done() 397 if _, err := quotaClient.CreateConfig(ctx, "apb.CreateConfigRequest{ 398 Name: cfg.Name, 399 Config: cfg, 400 }); err != nil { 401 t.Errorf("%v: CreateConfig() returned err = %v", cfg.Name, err) 402 createErrs <- true 403 } 404 }() 405 } 406 wg.Wait() 407 select { 408 case <-createErrs: 409 return 410 default: 411 } 412 413 const routinesPerConfig = 4 414 const updatesPerRoutine = 100 415 for _, cfg := range configs { 416 for num := 0; num < routinesPerConfig; num++ { 417 wg.Add(1) 418 go func(num int, want quotapb.Config) { 419 defer wg.Done() 420 baseTokens := 1 + rand.Intn(routinesPerConfig*100) 421 reset := num%2 == 0 422 for i := 0; i < updatesPerRoutine; i++ { 423 tokens := int64(baseTokens + i) 424 got, err := quotaClient.UpdateConfig(ctx, "apb.UpdateConfigRequest{ 425 Name: want.Name, 426 Config: "apb.Config{MaxTokens: tokens}, 427 UpdateMask: &field_mask.FieldMask{Paths: []string{"max_tokens"}}, 428 ResetQuota: reset, 429 }) 430 if err != nil { 431 t.Errorf("%v: UpdateConfig() returned err = %v", want.Name, err) 432 continue 433 } 434 435 want.CurrentTokens = got.CurrentTokens // Not important for this test 436 want.MaxTokens = tokens 437 if !proto.Equal(got, &want) { 438 diff := pretty.Compare(got, &want) 439 t.Errorf("%v: post-UpdateConfig() diff (-got +want):\n%v", want.Name, diff) 440 } 441 } 442 }(num, *cfg) 443 } 444 } 445 wg.Wait() 446 } 447 448 // upsertTest represents either a CreateConfig or UpdateConfig test. 449 type upsertTest struct { 450 desc string 451 // baseCfg is created before calling the RPC, if non-nil. 452 baseCfg *quotapb.Config 453 req upsertRequest 454 wantCode codes.Code 455 wantCfg *quotapb.Config 456 } 457 458 type upsertRequest interface { 459 GetName() string 460 } 461 462 type upsertRPC func(ctx context.Context, req interface{}) (*quotapb.Config, error) 463 464 // runUpsertTest runs either CreateConfig or UpdateConfig tests, depending on the supplied rpc. 465 // Storage is reset() before the test is executed. 466 func runUpsertTest(ctx context.Context, test upsertTest, rpc upsertRPC, rpcName string) error { 467 if err := reset(ctx); err != nil { 468 return fmt.Errorf("%v: reset() returned err = %v", test.desc, err) 469 } 470 if test.baseCfg != nil { 471 if _, err := quotaClient.CreateConfig(ctx, "apb.CreateConfigRequest{ 472 Name: test.baseCfg.Name, 473 Config: test.baseCfg, 474 }); err != nil { 475 return fmt.Errorf("%v: CreateConfig() returned err = %v", test.desc, err) 476 } 477 } 478 479 cfg, err := rpc(ctx, test.req) 480 switch s, ok := status.FromError(err); { 481 case !ok || s.Code() != test.wantCode: 482 return fmt.Errorf("%v: %v() returned err = %v, wantCode = %s", test.desc, rpcName, err, test.wantCode) 483 case test.wantCode == codes.OK && !proto.Equal(cfg, test.wantCfg): 484 return fmt.Errorf("%v: post-%v() diff:\n%v", test.desc, rpcName, pretty.Compare(cfg, test.wantCfg)) 485 case test.wantCfg == nil: 486 return nil 487 } 488 489 switch stored, err := quotaClient.GetConfig(ctx, "apb.GetConfigRequest{Name: test.req.GetName()}); { 490 case err != nil: 491 return fmt.Errorf("%v: GetConfig() returned err = %v", test.desc, err) 492 case !proto.Equal(stored, test.wantCfg): 493 return fmt.Errorf("%v: post-GetConfig() diff:\n%v", test.desc, pretty.Compare(stored, test.wantCfg)) 494 } 495 496 return nil 497 } 498 499 func TestServer_DeleteConfig(t *testing.T) { 500 tests := []struct { 501 desc string 502 createCfgs, wantCfgs []*quotapb.Config 503 req *quotapb.DeleteConfigRequest 504 }{ 505 { 506 desc: "success", 507 createCfgs: []*quotapb.Config{ 508 globalRead, globalWrite, 509 tree1Read, tree1Write, 510 tree2Read, tree2Write, 511 userRead, userWrite, 512 }, 513 wantCfgs: []*quotapb.Config{ 514 globalRead, globalWrite, 515 tree1Write, 516 tree2Read, tree2Write, 517 userRead, userWrite, 518 }, 519 req: "apb.DeleteConfigRequest{Name: tree1Read.Name}, 520 }, 521 { 522 desc: "lastConfig", 523 createCfgs: []*quotapb.Config{tree1Read}, 524 req: "apb.DeleteConfigRequest{Name: tree1Read.Name}, 525 }, 526 { 527 desc: "global", 528 createCfgs: []*quotapb.Config{globalRead, globalWrite}, 529 wantCfgs: []*quotapb.Config{globalRead}, 530 req: "apb.DeleteConfigRequest{Name: globalWrite.Name}, 531 }, 532 { 533 desc: "user", 534 createCfgs: []*quotapb.Config{userRead, userWrite}, 535 wantCfgs: []*quotapb.Config{userWrite}, 536 req: "apb.DeleteConfigRequest{Name: userRead.Name}, 537 }, 538 } 539 540 ctx := context.Background() 541 for _, test := range tests { 542 if err := reset(ctx); err != nil { 543 t.Errorf("%v: reset() returned err = %v", test.desc, err) 544 continue 545 } 546 547 for _, cfg := range test.createCfgs { 548 if _, err := quotaClient.CreateConfig(ctx, "apb.CreateConfigRequest{ 549 Name: cfg.Name, 550 Config: cfg, 551 }); err != nil { 552 t.Fatalf("%v: CreateConfig(%q) returned err = %v", test.desc, cfg.Name, err) 553 } 554 } 555 556 if _, err := quotaClient.DeleteConfig(ctx, test.req); err != nil { 557 t.Errorf("%v: DeleteConfig(%q) returned err = %v", test.desc, test.req.Name, err) 558 continue 559 } 560 561 resp, err := quotaClient.ListConfigs(ctx, "apb.ListConfigsRequest{ 562 View: quotapb.ListConfigsRequest_FULL, 563 }) 564 if err != nil { 565 t.Errorf("%v: ListConfigs() returned err = %v", test.desc, err) 566 continue 567 } 568 if err := sortAndCompare(resp.Configs, test.wantCfgs); err != nil { 569 t.Errorf("%v: post-DeleteConfig() %v", test.desc, err) 570 } 571 } 572 } 573 574 func TestServer_DeleteConfigErrors(t *testing.T) { 575 tests := []struct { 576 desc string 577 req *quotapb.DeleteConfigRequest 578 wantCode codes.Code 579 }{ 580 { 581 desc: "emptyName", 582 req: "apb.DeleteConfigRequest{}, 583 wantCode: codes.InvalidArgument, 584 }, 585 { 586 desc: "badName", 587 req: "apb.DeleteConfigRequest{Name: "bad/quota/name"}, 588 wantCode: codes.InvalidArgument, 589 }, 590 { 591 desc: "unknown", 592 req: "apb.DeleteConfigRequest{Name: globalWrite.Name}, 593 wantCode: codes.NotFound, 594 }, 595 } 596 597 ctx := context.Background() 598 for _, test := range tests { 599 _, err := quotaClient.DeleteConfig(ctx, test.req) 600 if s, ok := status.FromError(err); !ok || s.Code() != test.wantCode { 601 t.Errorf("%v: DeleteConfig() returned err = %v, wantCode = %s", test.desc, err, test.wantCode) 602 } 603 } 604 } 605 606 func TestServer_GetConfigErrors(t *testing.T) { 607 ctx := context.Background() 608 if err := reset(ctx); err != nil { 609 t.Fatalf("reset() returned err = %v", err) 610 } 611 612 tests := []struct { 613 desc string 614 req *quotapb.GetConfigRequest 615 wantCode codes.Code 616 }{ 617 { 618 desc: "emptyName", 619 req: "apb.GetConfigRequest{}, 620 wantCode: codes.InvalidArgument, 621 }, 622 { 623 desc: "badName", 624 req: "apb.GetConfigRequest{Name: "not/a/config/name"}, 625 wantCode: codes.InvalidArgument, 626 }, 627 { 628 desc: "notFound", 629 req: "apb.GetConfigRequest{Name: "quotas/global/write/config"}, 630 wantCode: codes.NotFound, 631 }, 632 } 633 for _, test := range tests { 634 // GetConfig's success return is tested as a consequence of other test cases. 635 _, err := quotaClient.GetConfig(ctx, test.req) 636 if s, ok := status.FromError(err); !ok || s.Code() != test.wantCode { 637 t.Errorf("%v: GetConfig() returned err = %v, wantCode = %s", test.desc, err, test.wantCode) 638 } 639 } 640 } 641 642 func TestServer_ListConfigs(t *testing.T) { 643 ctx := context.Background() 644 if err := reset(ctx); err != nil { 645 t.Fatalf("reset() returned err = %v", err) 646 } 647 648 // Test listing with an empty config first, so we can work with a fixed list of configs from 649 // here on 650 resp, err := quotaClient.ListConfigs(ctx, "apb.ListConfigsRequest{}) 651 switch { 652 case err != nil: 653 t.Errorf("empty: ListConfigs() returned err = %v", err) 654 case len(resp.Configs) != 0: 655 t.Errorf("empty: ListConfigs() returned >0 results: %v", err) 656 } 657 658 configs := []*quotapb.Config{ 659 globalRead, globalWrite, 660 tree1Read, tree1Write, 661 tree2Read, tree2Write, 662 userRead, userWrite, 663 } 664 for _, cfg := range configs { 665 if _, err := quotaClient.CreateConfig(ctx, "apb.CreateConfigRequest{ 666 Name: cfg.Name, 667 Config: cfg, 668 }); err != nil { 669 t.Fatalf("%q: CreateConfig() returned err = %v", cfg.Name, err) 670 } 671 } 672 673 basicGlobalRead := "apb.Config{Name: globalRead.Name} 674 basicGlobalWrite := "apb.Config{Name: globalWrite.Name} 675 basicTree1Read := "apb.Config{Name: tree1Read.Name} 676 basicTree1Write := "apb.Config{Name: tree1Write.Name} 677 basicTree2Read := "apb.Config{Name: tree2Read.Name} 678 basicTree2Write := "apb.Config{Name: tree2Write.Name} 679 basicUserRead := "apb.Config{Name: userRead.Name} 680 basicUserWrite := "apb.Config{Name: userWrite.Name} 681 682 tests := []struct { 683 desc string 684 req *quotapb.ListConfigsRequest 685 wantCfgs []*quotapb.Config 686 }{ 687 { 688 desc: "allBasicView", 689 req: "apb.ListConfigsRequest{}, 690 wantCfgs: []*quotapb.Config{ 691 basicGlobalRead, basicGlobalWrite, 692 basicTree1Read, basicTree1Write, 693 basicTree2Read, basicTree2Write, 694 basicUserRead, basicUserWrite, 695 }, 696 }, 697 { 698 desc: "allFullView", 699 req: "apb.ListConfigsRequest{View: quotapb.ListConfigsRequest_FULL}, 700 wantCfgs: configs, 701 }, 702 { 703 desc: "allTrees", 704 req: "apb.ListConfigsRequest{Names: []string{"quotas/trees/-/-/config"}}, 705 wantCfgs: []*quotapb.Config{basicTree1Read, basicTree1Write, basicTree2Read, basicTree2Write}, 706 }, 707 { 708 desc: "allUsers", 709 req: "apb.ListConfigsRequest{Names: []string{"quotas/users/-/-/config"}}, 710 wantCfgs: []*quotapb.Config{basicUserRead, basicUserWrite}, 711 }, 712 { 713 desc: "unknowns", 714 req: "apb.ListConfigsRequest{ 715 Names: []string{ 716 "quotas/trees/99997/read/config", 717 "quotas/trees/99998/write/config", 718 "quotas/trees/99999/-/config", 719 "quotas/users/unknown/-/config", 720 }, 721 }, 722 }, 723 } 724 for _, test := range tests { 725 resp, err := quotaClient.ListConfigs(ctx, test.req) 726 if err != nil { 727 t.Errorf("%v: ListConfigs() returned err = %v", test.desc, err) 728 continue 729 } 730 if err := sortAndCompare(resp.Configs, test.wantCfgs); err != nil { 731 t.Errorf("%v: post-ListConfigs() %v", test.desc, err) 732 } 733 } 734 } 735 736 func sortAndCompare(got, want []*quotapb.Config) error { 737 sort.Slice(got, func(i, j int) bool { return strings.Compare(got[i].Name, got[j].Name) == -1 }) 738 sort.Slice(want, func(i, j int) bool { return strings.Compare(want[i].Name, want[j].Name) == -1 }) 739 if len(got) != len(want) { 740 return fmt.Errorf("got %v configs, want %v", len(got), len(want)) 741 } 742 for i, cfg := range want { 743 if !proto.Equal(got[i], cfg) { 744 return fmt.Errorf("diff (-got +want):\n%v", pretty.Compare(got, want)) 745 } 746 } 747 return nil 748 } 749 750 func TestServer_ListConfigsErrors(t *testing.T) { 751 tests := []struct { 752 desc string 753 req *quotapb.ListConfigsRequest 754 wantCode codes.Code 755 }{ 756 { 757 desc: "badNameFilter", 758 req: "apb.ListConfigsRequest{Names: []string{"bad/quota/name"}}, 759 wantCode: codes.InvalidArgument, 760 }, 761 } 762 763 ctx := context.Background() 764 for _, test := range tests { 765 _, err := quotaClient.ListConfigs(ctx, test.req) 766 if s, ok := status.FromError(err); !ok || s.Code() != test.wantCode { 767 t.Errorf("%v: ListConfigs() returned err = %v, wantCode = %s", test.desc, err, test.wantCode) 768 } 769 } 770 } 771 772 func reset(ctx context.Context) error { 773 qs := storage.QuotaStorage{Client: etcdClient} 774 _, err := qs.UpdateConfigs(ctx, true /* reset */, func(c *storagepb.Configs) { *c = storagepb.Configs{} }) 775 return err 776 } 777 778 func startServer(etcdClient *clientv3.Client) (quotapb.QuotaClient, func(), error) { 779 var lis net.Listener 780 var s *grpc.Server 781 var conn *grpc.ClientConn 782 783 cleanup := func() { 784 if conn != nil { 785 conn.Close() 786 } 787 if s != nil { 788 s.GracefulStop() 789 } 790 if lis != nil { 791 lis.Close() 792 } 793 } 794 795 var err error 796 lis, err = net.Listen("tcp", "localhost:0") 797 if err != nil { 798 cleanup() 799 return nil, nil, err 800 } 801 802 s = grpc.NewServer(grpc.UnaryInterceptor(interceptor.ErrorWrapper)) 803 quotapb.RegisterQuotaServer(s, NewServer(etcdClient)) 804 go s.Serve(lis) 805 806 conn, err = grpc.Dial(lis.Addr().String(), grpc.WithInsecure()) 807 if err != nil { 808 cleanup() 809 return nil, nil, err 810 } 811 812 quotaClient := quotapb.NewQuotaClient(conn) 813 return quotaClient, cleanup, nil 814 }