vitess.io/vitess@v0.16.2/go/vt/vtadmin/api_test.go (about) 1 /* 2 Copyright 2020 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vtadmin 18 19 import ( 20 "context" 21 "encoding/base64" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "net/http" 26 "net/http/httptest" 27 "net/url" 28 "os" 29 "testing" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 "google.golang.org/protobuf/proto" 34 35 _flag "vitess.io/vitess/go/internal/flag" 36 "vitess.io/vitess/go/test/utils" 37 "vitess.io/vitess/go/vt/topo" 38 "vitess.io/vitess/go/vt/topo/memorytopo" 39 "vitess.io/vitess/go/vt/topo/topoproto" 40 "vitess.io/vitess/go/vt/vtadmin/cluster" 41 "vitess.io/vitess/go/vt/vtadmin/cluster/discovery/fakediscovery" 42 vtadminerrors "vitess.io/vitess/go/vt/vtadmin/errors" 43 vtadmintestutil "vitess.io/vitess/go/vt/vtadmin/testutil" 44 "vitess.io/vitess/go/vt/vtadmin/vtctldclient/fakevtctldclient" 45 "vitess.io/vitess/go/vt/vtctl/grpcvtctldserver" 46 "vitess.io/vitess/go/vt/vtctl/grpcvtctldserver/testutil" 47 "vitess.io/vitess/go/vt/vtctl/vtctldclient" 48 "vitess.io/vitess/go/vt/vttablet/tmclient" 49 "vitess.io/vitess/go/vt/vttablet/tmclienttest" 50 51 querypb "vitess.io/vitess/go/vt/proto/query" 52 tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" 53 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 54 vschemapb "vitess.io/vitess/go/vt/proto/vschema" 55 vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin" 56 vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" 57 vtctlservicepb "vitess.io/vitess/go/vt/proto/vtctlservice" 58 "vitess.io/vitess/go/vt/proto/vttime" 59 ) 60 61 func TestMain(m *testing.M) { 62 _flag.ParseFlagsForTest() 63 os.Exit(m.Run()) 64 } 65 66 func TestFindSchema(t *testing.T) { 67 t.Parallel() 68 69 tests := []struct { 70 name string 71 clusters []vtadmintestutil.TestClusterConfig 72 req *vtadminpb.FindSchemaRequest 73 expected *vtadminpb.Schema 74 shouldErr bool 75 }{ 76 { 77 name: "exact match", 78 clusters: []vtadmintestutil.TestClusterConfig{ 79 { 80 Cluster: &vtadminpb.Cluster{ 81 Id: "c1", 82 Name: "cluster1", 83 }, 84 VtctldClient: &fakevtctldclient.VtctldClient{ 85 GetKeyspacesResults: &struct { 86 Keyspaces []*vtctldatapb.Keyspace 87 Error error 88 }{ 89 Keyspaces: []*vtctldatapb.Keyspace{ 90 { 91 Name: "testkeyspace", 92 }, 93 }, 94 }, 95 GetSchemaResults: map[string]struct { 96 Response *vtctldatapb.GetSchemaResponse 97 Error error 98 }{ 99 "zone1-0000000100": { 100 Response: &vtctldatapb.GetSchemaResponse{ 101 Schema: &tabletmanagerdatapb.SchemaDefinition{ 102 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 103 { 104 Name: "testtable", 105 }, 106 }, 107 }, 108 }, 109 }, 110 }, 111 FindAllShardsInKeyspaceResults: map[string]struct { 112 Response *vtctldatapb.FindAllShardsInKeyspaceResponse 113 Error error 114 }{ 115 "testkeyspace": { 116 Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{ 117 Shards: map[string]*vtctldatapb.Shard{ 118 "-": { 119 Shard: &topodatapb.Shard{ 120 IsPrimaryServing: true, 121 }, 122 }, 123 }, 124 }, 125 }, 126 }, 127 }, 128 Tablets: []*vtadminpb.Tablet{ 129 { 130 Tablet: &topodatapb.Tablet{ 131 Alias: &topodatapb.TabletAlias{ 132 Cell: "zone1", 133 Uid: 100, 134 }, 135 Keyspace: "testkeyspace", 136 Shard: "-", 137 }, 138 State: vtadminpb.Tablet_SERVING, 139 }, 140 }, 141 }, 142 }, 143 req: &vtadminpb.FindSchemaRequest{ 144 Table: "testtable", 145 }, 146 expected: &vtadminpb.Schema{ 147 Cluster: &vtadminpb.Cluster{ 148 Id: "c1", 149 Name: "cluster1", 150 }, 151 Keyspace: "testkeyspace", 152 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 153 { 154 Name: "testtable", 155 }, 156 }, 157 TableSizes: map[string]*vtadminpb.Schema_TableSize{}, 158 }, 159 shouldErr: false, 160 }, 161 { 162 name: "error getting tablets", 163 clusters: []vtadmintestutil.TestClusterConfig{ 164 { 165 Cluster: &vtadminpb.Cluster{ 166 Id: "c1", 167 Name: "cluster1", 168 }, 169 170 DBConfig: vtadmintestutil.Dbcfg{ 171 ShouldErr: true, 172 }, 173 VtctldClient: &fakevtctldclient.VtctldClient{ 174 GetKeyspacesResults: &struct { 175 Keyspaces []*vtctldatapb.Keyspace 176 Error error 177 }{ 178 Keyspaces: []*vtctldatapb.Keyspace{ 179 {Name: "testkeyspace"}, 180 }, 181 }, 182 }, 183 }, 184 }, 185 req: &vtadminpb.FindSchemaRequest{ 186 Table: "testtable", 187 }, 188 shouldErr: true, 189 }, 190 { 191 name: "error getting keyspaces", 192 clusters: []vtadmintestutil.TestClusterConfig{ 193 { 194 Cluster: &vtadminpb.Cluster{ 195 Id: "c1", 196 Name: "cluster1", 197 }, 198 VtctldClient: &fakevtctldclient.VtctldClient{ 199 GetKeyspacesResults: &struct { 200 Keyspaces []*vtctldatapb.Keyspace 201 Error error 202 }{ 203 Error: fmt.Errorf("GetKeyspaces: %w", assert.AnError), 204 }, 205 }, 206 Tablets: []*vtadminpb.Tablet{ 207 { 208 Tablet: &topodatapb.Tablet{ 209 Alias: &topodatapb.TabletAlias{ 210 Cell: "zone1", 211 Uid: 100, 212 }, 213 Keyspace: "testkeyspace", 214 }, 215 State: vtadminpb.Tablet_SERVING, 216 }, 217 }, 218 }, 219 }, 220 req: &vtadminpb.FindSchemaRequest{ 221 Table: "testtable", 222 }, 223 shouldErr: true, 224 }, 225 { 226 name: "error getting schemas", 227 clusters: []vtadmintestutil.TestClusterConfig{ 228 { 229 Cluster: &vtadminpb.Cluster{ 230 Id: "c1", 231 Name: "cluster1", 232 }, 233 VtctldClient: &fakevtctldclient.VtctldClient{ 234 GetKeyspacesResults: &struct { 235 Keyspaces []*vtctldatapb.Keyspace 236 Error error 237 }{ 238 Keyspaces: []*vtctldatapb.Keyspace{ 239 { 240 Name: "testkeyspace", 241 }, 242 }, 243 }, 244 GetSchemaResults: map[string]struct { 245 Response *vtctldatapb.GetSchemaResponse 246 Error error 247 }{ 248 "zone1-0000000100": { 249 Error: fmt.Errorf("GetSchema: %w", assert.AnError), 250 }, 251 }, 252 }, 253 Tablets: []*vtadminpb.Tablet{ 254 { 255 Tablet: &topodatapb.Tablet{ 256 Alias: &topodatapb.TabletAlias{ 257 Cell: "zone1", 258 Uid: 100, 259 }, 260 Keyspace: "testkeyspace", 261 }, 262 State: vtadminpb.Tablet_SERVING, 263 }, 264 }, 265 }, 266 }, 267 req: &vtadminpb.FindSchemaRequest{ 268 Table: "testtable", 269 }, 270 shouldErr: true, 271 }, 272 { 273 name: "no schema found", 274 clusters: []vtadmintestutil.TestClusterConfig{ 275 { 276 Cluster: &vtadminpb.Cluster{ 277 Id: "c1", 278 Name: "cluster1", 279 }, 280 VtctldClient: &fakevtctldclient.VtctldClient{ 281 GetKeyspacesResults: &struct { 282 Keyspaces []*vtctldatapb.Keyspace 283 Error error 284 }{ 285 Keyspaces: []*vtctldatapb.Keyspace{ 286 { 287 Name: "testkeyspace", 288 }, 289 }, 290 }, 291 GetSchemaResults: map[string]struct { 292 Response *vtctldatapb.GetSchemaResponse 293 Error error 294 }{ 295 "zone1-0000000100": { 296 Response: &vtctldatapb.GetSchemaResponse{ 297 Schema: &tabletmanagerdatapb.SchemaDefinition{ 298 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 299 { 300 Name: "othertable", 301 }, 302 }, 303 }, 304 }, 305 }, 306 }, 307 }, 308 Tablets: []*vtadminpb.Tablet{ 309 { 310 Tablet: &topodatapb.Tablet{ 311 Alias: &topodatapb.TabletAlias{ 312 Cell: "zone1", 313 Uid: 100, 314 }, 315 Keyspace: "testkeyspace", 316 }, 317 State: vtadminpb.Tablet_SERVING, 318 }, 319 }, 320 }, 321 }, 322 req: &vtadminpb.FindSchemaRequest{ 323 Table: "testtable", 324 }, 325 shouldErr: true, 326 }, 327 { 328 name: "ambiguous schema errors", 329 clusters: []vtadmintestutil.TestClusterConfig{ 330 { 331 Cluster: &vtadminpb.Cluster{ 332 Id: "c1", 333 Name: "cluster1", 334 }, 335 VtctldClient: &fakevtctldclient.VtctldClient{ 336 GetKeyspacesResults: &struct { 337 Keyspaces []*vtctldatapb.Keyspace 338 Error error 339 }{ 340 Keyspaces: []*vtctldatapb.Keyspace{ 341 { 342 Name: "testkeyspace", 343 }, 344 }, 345 }, 346 GetSchemaResults: map[string]struct { 347 Response *vtctldatapb.GetSchemaResponse 348 Error error 349 }{ 350 "zone1-0000000100": { 351 Response: &vtctldatapb.GetSchemaResponse{ 352 Schema: &tabletmanagerdatapb.SchemaDefinition{ 353 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 354 { 355 Name: "testtable", 356 }, 357 }, 358 }, 359 }, 360 }, 361 }, 362 }, 363 Tablets: []*vtadminpb.Tablet{ 364 { 365 Tablet: &topodatapb.Tablet{ 366 Alias: &topodatapb.TabletAlias{ 367 Cell: "zone1", 368 Uid: 100, 369 }, 370 Keyspace: "testkeyspace", 371 }, 372 State: vtadminpb.Tablet_SERVING, 373 }, 374 }, 375 }, 376 { 377 Cluster: &vtadminpb.Cluster{ 378 Id: "c2", 379 Name: "cluster2", 380 }, 381 VtctldClient: &fakevtctldclient.VtctldClient{ 382 GetKeyspacesResults: &struct { 383 Keyspaces []*vtctldatapb.Keyspace 384 Error error 385 }{ 386 Keyspaces: []*vtctldatapb.Keyspace{ 387 { 388 Name: "testkeyspace", 389 }, 390 }, 391 }, 392 GetSchemaResults: map[string]struct { 393 Response *vtctldatapb.GetSchemaResponse 394 Error error 395 }{ 396 "zone2-0000000200": { 397 Response: &vtctldatapb.GetSchemaResponse{ 398 Schema: &tabletmanagerdatapb.SchemaDefinition{ 399 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 400 { 401 Name: "testtable", 402 }, 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 Tablets: []*vtadminpb.Tablet{ 410 { 411 Tablet: &topodatapb.Tablet{ 412 Alias: &topodatapb.TabletAlias{ 413 Cell: "zone2", 414 Uid: 200, 415 }, 416 Keyspace: "testkeyspace", 417 }, 418 State: vtadminpb.Tablet_SERVING, 419 }, 420 }, 421 }, 422 }, 423 req: &vtadminpb.FindSchemaRequest{ 424 Table: "testtable", 425 }, 426 shouldErr: true, 427 }, 428 { 429 name: "ambiguous schema with request scoped to single cluster passes", 430 clusters: []vtadmintestutil.TestClusterConfig{ 431 { 432 Cluster: &vtadminpb.Cluster{ 433 Id: "c1", 434 Name: "cluster1", 435 }, 436 VtctldClient: &fakevtctldclient.VtctldClient{ 437 GetKeyspacesResults: &struct { 438 Keyspaces []*vtctldatapb.Keyspace 439 Error error 440 }{ 441 Keyspaces: []*vtctldatapb.Keyspace{ 442 { 443 Name: "testkeyspace1", 444 }, 445 }, 446 }, 447 GetSchemaResults: map[string]struct { 448 Response *vtctldatapb.GetSchemaResponse 449 Error error 450 }{ 451 "zone1-0000000100": { 452 Response: &vtctldatapb.GetSchemaResponse{ 453 Schema: &tabletmanagerdatapb.SchemaDefinition{ 454 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 455 { 456 Name: "testtable", 457 }, 458 }, 459 }, 460 }, 461 }, 462 }, 463 }, 464 Tablets: []*vtadminpb.Tablet{ 465 { 466 Tablet: &topodatapb.Tablet{ 467 Alias: &topodatapb.TabletAlias{ 468 Cell: "zone1", 469 Uid: 100, 470 }, 471 Keyspace: "testkeyspace1", 472 }, 473 State: vtadminpb.Tablet_SERVING, 474 }, 475 }, 476 }, 477 { 478 Cluster: &vtadminpb.Cluster{ 479 Id: "c2", 480 Name: "cluster2", 481 }, 482 VtctldClient: &fakevtctldclient.VtctldClient{ 483 GetKeyspacesResults: &struct { 484 Keyspaces []*vtctldatapb.Keyspace 485 Error error 486 }{ 487 Keyspaces: []*vtctldatapb.Keyspace{ 488 { 489 Name: "testkeyspace2", 490 }, 491 }, 492 }, 493 GetSchemaResults: map[string]struct { 494 Response *vtctldatapb.GetSchemaResponse 495 Error error 496 }{ 497 "zone2-0000000200": { 498 Response: &vtctldatapb.GetSchemaResponse{ 499 Schema: &tabletmanagerdatapb.SchemaDefinition{ 500 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 501 { 502 Name: "testtable", 503 }, 504 }, 505 }, 506 }, 507 }, 508 }, 509 }, 510 Tablets: []*vtadminpb.Tablet{ 511 { 512 Tablet: &topodatapb.Tablet{ 513 Alias: &topodatapb.TabletAlias{ 514 Cell: "zone2", 515 Uid: 200, 516 }, 517 Keyspace: "testkeyspace2", 518 }, 519 State: vtadminpb.Tablet_SERVING, 520 }, 521 }, 522 }, 523 }, 524 req: &vtadminpb.FindSchemaRequest{ 525 Table: "testtable", 526 ClusterIds: []string{"c1"}, 527 }, 528 expected: &vtadminpb.Schema{ 529 Cluster: &vtadminpb.Cluster{ 530 Id: "c1", 531 Name: "cluster1", 532 }, 533 Keyspace: "testkeyspace1", 534 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 535 { 536 Name: "testtable", 537 }, 538 }, 539 TableSizes: map[string]*vtadminpb.Schema_TableSize{}, 540 }, 541 shouldErr: false, 542 }, 543 } 544 545 ctx := context.Background() 546 547 for _, tt := range tests { 548 tt := tt 549 550 t.Run(tt.name, func(t *testing.T) { 551 t.Parallel() 552 553 clusters := make([]*cluster.Cluster, len(tt.clusters)) 554 for i, cfg := range tt.clusters { 555 clusters[i] = vtadmintestutil.BuildCluster(t, cfg) 556 } 557 558 api := NewAPI(clusters, Options{}) 559 defer api.Close() 560 561 resp, err := api.FindSchema(ctx, tt.req) 562 if tt.shouldErr { 563 assert.Error(t, err) 564 565 return 566 } 567 568 assert.NoError(t, err) 569 assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp) 570 }) 571 } 572 573 t.Run("size aggregation", func(t *testing.T) { 574 t.Parallel() 575 576 c1pb := &vtadminpb.Cluster{ 577 Id: "c1", 578 Name: "cluster1", 579 } 580 c2pb := &vtadminpb.Cluster{ 581 Id: "c2", 582 Name: "cluster2", 583 } 584 585 c1 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 586 Cluster: c1pb, 587 VtctldClient: &fakevtctldclient.VtctldClient{ 588 FindAllShardsInKeyspaceResults: map[string]struct { 589 Response *vtctldatapb.FindAllShardsInKeyspaceResponse 590 Error error 591 }{ 592 "testkeyspace": { 593 Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{ 594 Shards: map[string]*vtctldatapb.Shard{ 595 "-80": { 596 Keyspace: "testkeyspace", 597 Name: "-80", 598 Shard: &topodatapb.Shard{ 599 IsPrimaryServing: true, 600 PrimaryAlias: &topodatapb.TabletAlias{ 601 Cell: "c1zone1", 602 Uid: 100, 603 }, 604 }, 605 }, 606 "80-": { 607 Keyspace: "testkeyspace", 608 Name: "80-", 609 Shard: &topodatapb.Shard{ 610 IsPrimaryServing: true, 611 PrimaryAlias: &topodatapb.TabletAlias{ 612 Cell: "c1zone1", 613 Uid: 200, 614 }, 615 }, 616 }, 617 }, 618 }, 619 }, 620 "ks1": { 621 Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{ 622 Shards: map[string]*vtctldatapb.Shard{ 623 "-": { 624 Keyspace: "ks1", 625 Name: "-", 626 Shard: &topodatapb.Shard{ 627 IsPrimaryServing: true, 628 PrimaryAlias: &topodatapb.TabletAlias{ 629 Cell: "c1zone1", 630 Uid: 300, 631 }, 632 }, 633 }, 634 }, 635 }, 636 }, 637 }, 638 GetKeyspacesResults: &struct { 639 Keyspaces []*vtctldatapb.Keyspace 640 Error error 641 }{ 642 Keyspaces: []*vtctldatapb.Keyspace{ 643 {Name: "testkeyspace"}, 644 {Name: "ks1"}, 645 }, 646 }, 647 GetSchemaResults: map[string]struct { 648 Response *vtctldatapb.GetSchemaResponse 649 Error error 650 }{ 651 "c1zone1-0000000100": { 652 Response: &vtctldatapb.GetSchemaResponse{ 653 Schema: &tabletmanagerdatapb.SchemaDefinition{ 654 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 655 { 656 Name: "testtable", 657 RowCount: 10, 658 DataLength: 100, 659 }, 660 }, 661 }, 662 }, 663 }, 664 "c1zone1-0000000200": { 665 Response: &vtctldatapb.GetSchemaResponse{ 666 Schema: &tabletmanagerdatapb.SchemaDefinition{ 667 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 668 { 669 Name: "testtable", 670 RowCount: 20, 671 DataLength: 200, 672 }, 673 }, 674 }, 675 }, 676 }, 677 }, 678 }, 679 Tablets: []*vtadminpb.Tablet{ 680 { 681 Cluster: c1pb, 682 Tablet: &topodatapb.Tablet{ 683 Alias: &topodatapb.TabletAlias{ 684 Cell: "c1zone1", 685 Uid: 100, 686 }, 687 Keyspace: "testkeyspace", 688 Shard: "-80", 689 }, 690 State: vtadminpb.Tablet_SERVING, 691 }, 692 { 693 Cluster: c1pb, 694 Tablet: &topodatapb.Tablet{ 695 Alias: &topodatapb.TabletAlias{ 696 Cell: "c1zone1", 697 Uid: 200, 698 }, 699 Keyspace: "testkeyspace", 700 Shard: "80-", 701 }, 702 State: vtadminpb.Tablet_SERVING, 703 }, 704 }, 705 }, 706 ) 707 c2 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 708 Cluster: c2pb, 709 VtctldClient: &fakevtctldclient.VtctldClient{ 710 FindAllShardsInKeyspaceResults: map[string]struct { 711 Response *vtctldatapb.FindAllShardsInKeyspaceResponse 712 Error error 713 }{ 714 "ks2": { 715 Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{ 716 Shards: map[string]*vtctldatapb.Shard{ 717 "-": { 718 Keyspace: "ks2", 719 Name: "-", 720 Shard: &topodatapb.Shard{ 721 IsPrimaryServing: true, 722 PrimaryAlias: &topodatapb.TabletAlias{ 723 Cell: "c2z1", 724 Uid: 100, 725 }, 726 }, 727 }, 728 }, 729 }, 730 }, 731 }, 732 GetKeyspacesResults: &struct { 733 Keyspaces []*vtctldatapb.Keyspace 734 Error error 735 }{ 736 Keyspaces: []*vtctldatapb.Keyspace{ 737 { 738 Name: "ks2", 739 }, 740 }, 741 }, 742 GetSchemaResults: map[string]struct { 743 Response *vtctldatapb.GetSchemaResponse 744 Error error 745 }{ 746 "c2z1-0000000100": { 747 Response: &vtctldatapb.GetSchemaResponse{}, 748 }, 749 }, 750 }, 751 Tablets: []*vtadminpb.Tablet{ 752 { 753 Cluster: c2pb, 754 Tablet: &topodatapb.Tablet{ 755 Alias: &topodatapb.TabletAlias{ 756 Cell: "c2z1", 757 Uid: 100, 758 }, 759 Keyspace: "ks2", 760 Shard: "-", 761 }, 762 State: vtadminpb.Tablet_SERVING, 763 }, 764 }, 765 }, 766 ) 767 768 api := NewAPI([]*cluster.Cluster{c1, c2}, Options{}) 769 defer api.Close() 770 771 schema, err := api.FindSchema(ctx, &vtadminpb.FindSchemaRequest{ 772 Table: "testtable", 773 TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{ 774 AggregateSizes: true, 775 }, 776 }) 777 778 expected := &vtadminpb.Schema{ 779 Cluster: c1pb, 780 Keyspace: "testkeyspace", 781 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 782 { 783 Name: "testtable", 784 }, 785 }, 786 TableSizes: map[string]*vtadminpb.Schema_TableSize{ 787 "testtable": { 788 RowCount: 10 + 20, 789 DataLength: 100 + 200, 790 ByShard: map[string]*vtadminpb.Schema_ShardTableSize{ 791 "-80": { 792 RowCount: 10, 793 DataLength: 100, 794 }, 795 "80-": { 796 RowCount: 20, 797 DataLength: 200, 798 }, 799 }, 800 }, 801 }, 802 } 803 804 if schema != nil { 805 // Clone so our mutation below doesn't trip the race detector. 806 schema = proto.Clone(schema).(*vtadminpb.Schema) 807 808 for _, td := range schema.TableDefinitions { 809 // Zero these out because they're non-deterministic and also not 810 // relevant to the final result. 811 td.RowCount = 0 812 td.DataLength = 0 813 } 814 } 815 816 assert.NoError(t, err) 817 assert.Truef(t, proto.Equal(expected, schema), "expected %v, got %v", expected, schema) 818 }) 819 } 820 821 func TestGetClusters(t *testing.T) { 822 t.Parallel() 823 824 tests := []struct { 825 name string 826 clusters []*cluster.Cluster 827 expected []*vtadminpb.Cluster 828 }{ 829 { 830 name: "multiple clusters", 831 clusters: []*cluster.Cluster{ 832 { 833 ID: "c1", 834 Name: "cluster1", 835 Discovery: fakediscovery.New(), 836 }, 837 { 838 ID: "c2", 839 Name: "cluster2", 840 Discovery: fakediscovery.New(), 841 }, 842 }, 843 expected: []*vtadminpb.Cluster{ 844 { 845 Id: "c1", 846 Name: "cluster1", 847 }, 848 { 849 Id: "c2", 850 Name: "cluster2", 851 }, 852 }, 853 }, 854 { 855 name: "no clusters", 856 clusters: []*cluster.Cluster{}, 857 expected: []*vtadminpb.Cluster{}, 858 }, 859 } 860 861 ctx := context.Background() 862 863 for _, tt := range tests { 864 tt := tt 865 866 t.Run(tt.name, func(t *testing.T) { 867 t.Parallel() 868 869 api := NewAPI(tt.clusters, Options{}) 870 871 resp, err := api.GetClusters(ctx, &vtadminpb.GetClustersRequest{}) 872 assert.NoError(t, err) 873 assert.ElementsMatch(t, tt.expected, resp.Clusters) 874 }) 875 } 876 } 877 878 func TestGetGates(t *testing.T) { 879 t.Parallel() 880 881 fakedisco1 := fakediscovery.New() 882 cluster1 := &cluster.Cluster{ 883 ID: "c1", 884 Name: "cluster1", 885 Discovery: fakedisco1, 886 } 887 cluster1Gates := []*vtadminpb.VTGate{ 888 { 889 Hostname: "cluster1-gate1", 890 }, 891 { 892 Hostname: "cluster1-gate2", 893 }, 894 { 895 Hostname: "cluster1-gate3", 896 }, 897 } 898 fakedisco1.AddTaggedGates(nil, cluster1Gates...) 899 900 expectedCluster1Gates := []*vtadminpb.VTGate{ 901 { 902 Cluster: &vtadminpb.Cluster{ 903 Id: cluster1.ID, 904 Name: cluster1.Name, 905 }, 906 Hostname: "cluster1-gate1", 907 }, 908 { 909 Cluster: &vtadminpb.Cluster{ 910 Id: cluster1.ID, 911 Name: cluster1.Name, 912 }, 913 Hostname: "cluster1-gate2", 914 }, 915 { 916 Cluster: &vtadminpb.Cluster{ 917 Id: cluster1.ID, 918 Name: cluster1.Name, 919 }, 920 Hostname: "cluster1-gate3", 921 }, 922 } 923 924 fakedisco2 := fakediscovery.New() 925 cluster2 := &cluster.Cluster{ 926 ID: "c2", 927 Name: "cluster2", 928 Discovery: fakedisco2, 929 } 930 cluster2Gates := []*vtadminpb.VTGate{ 931 { 932 Hostname: "cluster2-gate1", 933 }, 934 } 935 fakedisco2.AddTaggedGates(nil, cluster2Gates...) 936 937 expectedCluster2Gates := []*vtadminpb.VTGate{ 938 { 939 Cluster: &vtadminpb.Cluster{ 940 Id: cluster2.ID, 941 Name: cluster2.Name, 942 }, 943 Hostname: "cluster2-gate1", 944 }, 945 } 946 947 api := NewAPI([]*cluster.Cluster{cluster1, cluster2}, Options{}) 948 ctx := context.Background() 949 950 resp, err := api.GetGates(ctx, &vtadminpb.GetGatesRequest{}) 951 assert.NoError(t, err) 952 assert.ElementsMatch(t, append(expectedCluster1Gates, expectedCluster2Gates...), resp.Gates) 953 954 resp, err = api.GetGates(ctx, &vtadminpb.GetGatesRequest{ClusterIds: []string{cluster1.ID}}) 955 assert.NoError(t, err) 956 assert.ElementsMatch(t, expectedCluster1Gates, resp.Gates) 957 958 fakedisco1.SetGatesError(true) 959 960 resp, err = api.GetGates(ctx, &vtadminpb.GetGatesRequest{}) 961 assert.Error(t, err) 962 assert.Nil(t, resp) 963 } 964 965 func TestGetKeyspace(t *testing.T) { 966 t.Parallel() 967 968 tests := []struct { 969 name string 970 clusterShards [][]*vtctldatapb.Shard 971 req *vtadminpb.GetKeyspaceRequest 972 expected *vtadminpb.Keyspace 973 shouldErr bool 974 }{ 975 { 976 clusterShards: [][]*vtctldatapb.Shard{ 977 // cluster-0 978 { 979 { 980 Keyspace: "ks", 981 Name: "-80", 982 Shard: &topodatapb.Shard{}, 983 }, 984 { 985 Keyspace: "ks", 986 Name: "80-", 987 Shard: &topodatapb.Shard{}, 988 }, 989 }, 990 // cluster-1 991 { 992 { 993 Keyspace: "ks", 994 Name: "-", 995 Shard: &topodatapb.Shard{}, 996 }, 997 }, 998 }, 999 req: &vtadminpb.GetKeyspaceRequest{ 1000 ClusterId: "cluster-1", 1001 Keyspace: "ks", 1002 }, 1003 expected: &vtadminpb.Keyspace{ 1004 Cluster: &vtadminpb.Cluster{ 1005 Id: "cluster-1", 1006 Name: "cluster-1", 1007 }, 1008 Keyspace: &vtctldatapb.Keyspace{ 1009 Name: "ks", 1010 Keyspace: &topodatapb.Keyspace{}, 1011 }, 1012 Shards: map[string]*vtctldatapb.Shard{ 1013 "-": { 1014 Keyspace: "ks", 1015 Name: "-", 1016 Shard: &topodatapb.Shard{}, 1017 }, 1018 }, 1019 }, 1020 }, 1021 { 1022 name: "nonexistent cluster", 1023 clusterShards: [][]*vtctldatapb.Shard{ 1024 // cluster-0 1025 { 1026 { 1027 Keyspace: "ks", 1028 Name: "-80", 1029 Shard: &topodatapb.Shard{}, 1030 }, 1031 { 1032 Keyspace: "ks", 1033 Name: "80-", 1034 Shard: &topodatapb.Shard{}, 1035 }, 1036 }, 1037 // cluster-1 1038 { 1039 { 1040 Keyspace: "ks", 1041 Name: "-", 1042 Shard: &topodatapb.Shard{}, 1043 }, 1044 }, 1045 }, 1046 req: &vtadminpb.GetKeyspaceRequest{ 1047 ClusterId: "cluster-2", 1048 }, 1049 shouldErr: true, 1050 }, 1051 } 1052 1053 ctx := context.Background() 1054 1055 for _, tt := range tests { 1056 tt := tt 1057 1058 t.Run(tt.name, func(t *testing.T) { 1059 t.Parallel() 1060 1061 topos := make([]*topo.Server, len(tt.clusterShards)) 1062 vtctlds := make([]vtctlservicepb.VtctldServer, len(tt.clusterShards)) 1063 1064 for i, shards := range tt.clusterShards { 1065 ts := memorytopo.NewServer("cell1") 1066 testutil.AddShards(ctx, t, ts, shards...) 1067 topos[i] = ts 1068 vtctlds[i] = testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer { 1069 return grpcvtctldserver.NewVtctldServer(ts) 1070 }) 1071 } 1072 1073 testutil.WithTestServers(t, func(t *testing.T, clients ...vtctldclient.VtctldClient) { 1074 clusters := make([]*cluster.Cluster, len(clients)) 1075 for i, client := range clients { 1076 clusters[i] = vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 1077 Cluster: &vtadminpb.Cluster{ 1078 Id: fmt.Sprintf("cluster-%d", i), 1079 Name: fmt.Sprintf("cluster-%d", i), 1080 }, 1081 VtctldClient: client, 1082 }) 1083 } 1084 1085 api := NewAPI(clusters, Options{}) 1086 ks, err := api.GetKeyspace(ctx, tt.req) 1087 if tt.shouldErr { 1088 assert.Error(t, err) 1089 return 1090 } 1091 1092 assert.NoError(t, err) 1093 assert.Truef(t, proto.Equal(tt.expected, ks), "expected %v, got %v", tt.expected, ks) 1094 }, vtctlds...) 1095 }) 1096 } 1097 } 1098 1099 func TestGetKeyspaces(t *testing.T) { 1100 t.Parallel() 1101 1102 tests := []struct { 1103 name string 1104 clusterKeyspaces [][]*vtctldatapb.Keyspace 1105 clusterShards [][]*vtctldatapb.Shard 1106 req *vtadminpb.GetKeyspacesRequest 1107 expected *vtadminpb.GetKeyspacesResponse 1108 }{ 1109 { 1110 name: "multiple clusters, multiple shards", 1111 clusterKeyspaces: [][]*vtctldatapb.Keyspace{ 1112 // cluster0 1113 { 1114 { 1115 Name: "c0-ks0", 1116 Keyspace: &topodatapb.Keyspace{}, 1117 }, 1118 }, 1119 // cluster1 1120 { 1121 { 1122 Name: "c1-ks0", 1123 Keyspace: &topodatapb.Keyspace{}, 1124 }, 1125 }, 1126 }, 1127 clusterShards: [][]*vtctldatapb.Shard{ 1128 // cluster0 1129 { 1130 { 1131 Keyspace: "c0-ks0", 1132 Name: "-80", 1133 }, 1134 { 1135 Keyspace: "c0-ks0", 1136 Name: "80-", 1137 }, 1138 }, 1139 // cluster1 1140 { 1141 { 1142 Keyspace: "c1-ks0", 1143 Name: "-", 1144 }, 1145 }, 1146 }, 1147 req: &vtadminpb.GetKeyspacesRequest{}, 1148 expected: &vtadminpb.GetKeyspacesResponse{ 1149 Keyspaces: []*vtadminpb.Keyspace{ 1150 { 1151 Cluster: &vtadminpb.Cluster{ 1152 Id: "c0", 1153 Name: "cluster0", 1154 }, 1155 Keyspace: &vtctldatapb.Keyspace{ 1156 Name: "c0-ks0", 1157 Keyspace: &topodatapb.Keyspace{}, 1158 }, 1159 Shards: map[string]*vtctldatapb.Shard{ 1160 "-80": { 1161 Keyspace: "c0-ks0", 1162 Name: "-80", 1163 Shard: &topodatapb.Shard{ 1164 IsPrimaryServing: true, 1165 }, 1166 }, 1167 "80-": { 1168 Keyspace: "c0-ks0", 1169 Name: "80-", 1170 Shard: &topodatapb.Shard{ 1171 IsPrimaryServing: true, 1172 }, 1173 }, 1174 }, 1175 }, 1176 { 1177 Cluster: &vtadminpb.Cluster{ 1178 Id: "c1", 1179 Name: "cluster1", 1180 }, 1181 Keyspace: &vtctldatapb.Keyspace{ 1182 Name: "c1-ks0", 1183 Keyspace: &topodatapb.Keyspace{}, 1184 }, 1185 Shards: map[string]*vtctldatapb.Shard{ 1186 "-": { 1187 Keyspace: "c1-ks0", 1188 Name: "-", 1189 Shard: &topodatapb.Shard{ 1190 IsPrimaryServing: true, 1191 }, 1192 }, 1193 }, 1194 }, 1195 }, 1196 }, 1197 }, 1198 { 1199 name: "with snapshot", 1200 clusterKeyspaces: [][]*vtctldatapb.Keyspace{ 1201 // cluster0 1202 { 1203 { 1204 Name: "testkeyspace", 1205 Keyspace: &topodatapb.Keyspace{}, 1206 }, 1207 { 1208 Name: "snapshot", 1209 Keyspace: &topodatapb.Keyspace{ 1210 KeyspaceType: topodatapb.KeyspaceType_SNAPSHOT, 1211 BaseKeyspace: "testkeyspace", 1212 SnapshotTime: &vttime.Time{Seconds: 10, Nanoseconds: 1}, 1213 }, 1214 }, 1215 }, 1216 }, 1217 req: &vtadminpb.GetKeyspacesRequest{}, 1218 expected: &vtadminpb.GetKeyspacesResponse{ 1219 Keyspaces: []*vtadminpb.Keyspace{ 1220 { 1221 Cluster: &vtadminpb.Cluster{ 1222 Id: "c0", 1223 Name: "cluster0", 1224 }, 1225 Keyspace: &vtctldatapb.Keyspace{ 1226 Name: "testkeyspace", 1227 Keyspace: &topodatapb.Keyspace{}, 1228 }, 1229 }, 1230 { 1231 Cluster: &vtadminpb.Cluster{ 1232 Id: "c0", 1233 Name: "cluster0", 1234 }, 1235 Keyspace: &vtctldatapb.Keyspace{ 1236 Name: "snapshot", 1237 Keyspace: &topodatapb.Keyspace{ 1238 KeyspaceType: topodatapb.KeyspaceType_SNAPSHOT, 1239 BaseKeyspace: "testkeyspace", 1240 SnapshotTime: &vttime.Time{Seconds: 10, Nanoseconds: 1}, 1241 }, 1242 }, 1243 }, 1244 }, 1245 }, 1246 }, 1247 { 1248 name: "filtered by cluster ID", 1249 clusterKeyspaces: [][]*vtctldatapb.Keyspace{ 1250 // cluster0 1251 { 1252 { 1253 Name: "c0-ks0", 1254 Keyspace: &topodatapb.Keyspace{}, 1255 }, 1256 }, 1257 // cluster1 1258 { 1259 { 1260 Name: "c1-ks0", 1261 Keyspace: &topodatapb.Keyspace{}, 1262 }, 1263 }, 1264 }, 1265 req: &vtadminpb.GetKeyspacesRequest{ 1266 ClusterIds: []string{"c1"}, 1267 }, 1268 expected: &vtadminpb.GetKeyspacesResponse{ 1269 Keyspaces: []*vtadminpb.Keyspace{ 1270 { 1271 Cluster: &vtadminpb.Cluster{ 1272 Id: "c1", 1273 Name: "cluster1", 1274 }, 1275 Keyspace: &vtctldatapb.Keyspace{ 1276 Name: "c1-ks0", 1277 Keyspace: &topodatapb.Keyspace{}, 1278 }, 1279 }, 1280 }, 1281 }, 1282 }, 1283 } 1284 1285 ctx := context.Background() 1286 1287 for _, tt := range tests { 1288 tt := tt 1289 1290 t.Run(tt.name, func(t *testing.T) { 1291 t.Parallel() 1292 1293 // Note that these test cases were written prior to the existence of 1294 // WithTestServers, so they are all written with the assumption that 1295 // there are exactly 2 clusters. 1296 topos := []*topo.Server{ 1297 memorytopo.NewServer("c0_cell1"), 1298 memorytopo.NewServer("c1_cell1"), 1299 } 1300 1301 for cdx, cks := range tt.clusterKeyspaces { 1302 for _, ks := range cks { 1303 testutil.AddKeyspace(ctx, t, topos[cdx], ks) 1304 } 1305 } 1306 1307 for cdx, css := range tt.clusterShards { 1308 testutil.AddShards(ctx, t, topos[cdx], css...) 1309 } 1310 1311 servers := []vtctlservicepb.VtctldServer{ 1312 testutil.NewVtctldServerWithTabletManagerClient(t, topos[0], nil, func(ts *topo.Server) vtctlservicepb.VtctldServer { 1313 return grpcvtctldserver.NewVtctldServer(ts) 1314 }), 1315 testutil.NewVtctldServerWithTabletManagerClient(t, topos[1], nil, func(ts *topo.Server) vtctlservicepb.VtctldServer { 1316 return grpcvtctldserver.NewVtctldServer(ts) 1317 }), 1318 } 1319 1320 testutil.WithTestServers(t, func(t *testing.T, clients ...vtctldclient.VtctldClient) { 1321 clusters := []*cluster.Cluster{ 1322 vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 1323 Cluster: &vtadminpb.Cluster{ 1324 Id: "c0", 1325 Name: "cluster0", 1326 }, 1327 VtctldClient: clients[0], 1328 }), 1329 vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 1330 Cluster: &vtadminpb.Cluster{ 1331 Id: "c1", 1332 Name: "cluster1", 1333 }, 1334 VtctldClient: clients[1], 1335 }), 1336 } 1337 1338 api := NewAPI(clusters, Options{}) 1339 resp, err := api.GetKeyspaces(ctx, tt.req) 1340 require.NoError(t, err) 1341 1342 vtadmintestutil.AssertKeyspaceSlicesEqual(t, tt.expected.Keyspaces, resp.Keyspaces) 1343 }, servers...) 1344 }) 1345 } 1346 } 1347 1348 func TestGetSchema(t *testing.T) { 1349 t.Parallel() 1350 1351 tests := []struct { 1352 name string 1353 clusterID int 1354 ts *topo.Server 1355 tmc tmclient.TabletManagerClient 1356 tablets []*vtadminpb.Tablet 1357 req *vtadminpb.GetSchemaRequest 1358 expected *vtadminpb.Schema 1359 shouldErr bool 1360 }{ 1361 { 1362 name: "success", 1363 clusterID: 1, 1364 ts: memorytopo.NewServer("zone1"), 1365 tmc: &testutil.TabletManagerClient{ 1366 GetSchemaResults: map[string]struct { 1367 Schema *tabletmanagerdatapb.SchemaDefinition 1368 Error error 1369 }{ 1370 "zone1-0000000100": { 1371 Schema: &tabletmanagerdatapb.SchemaDefinition{ 1372 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1373 { 1374 Name: "testtable", 1375 }, 1376 }, 1377 }, 1378 }, 1379 }, 1380 }, 1381 tablets: []*vtadminpb.Tablet{ 1382 { 1383 Cluster: &vtadminpb.Cluster{ 1384 Id: "c1", 1385 Name: "cluster1", 1386 }, 1387 State: vtadminpb.Tablet_SERVING, 1388 Tablet: &topodatapb.Tablet{ 1389 Alias: &topodatapb.TabletAlias{ 1390 Cell: "zone1", 1391 Uid: 100, 1392 }, 1393 Keyspace: "testkeyspace", 1394 }, 1395 }, 1396 }, 1397 req: &vtadminpb.GetSchemaRequest{ 1398 ClusterId: "c1", 1399 Keyspace: "testkeyspace", 1400 Table: "testtable", 1401 }, 1402 expected: &vtadminpb.Schema{ 1403 Cluster: &vtadminpb.Cluster{ 1404 Id: "c1", 1405 Name: "cluster1", 1406 }, 1407 Keyspace: "testkeyspace", 1408 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1409 { 1410 Name: "testtable", 1411 }, 1412 }, 1413 }, 1414 shouldErr: false, 1415 }, 1416 { 1417 name: "cluster not found", 1418 clusterID: 1, // results in clusterId == "c1" 1419 ts: memorytopo.NewServer("zone1"), 1420 tablets: nil, 1421 req: &vtadminpb.GetSchemaRequest{ 1422 ClusterId: "c2", 1423 Keyspace: "testkeyspace", 1424 Table: "testtable", 1425 }, 1426 expected: nil, 1427 shouldErr: true, 1428 }, 1429 { 1430 name: "tablet not found for keyspace", 1431 clusterID: 1, 1432 ts: memorytopo.NewServer("zone1"), 1433 tablets: []*vtadminpb.Tablet{ 1434 { 1435 Cluster: &vtadminpb.Cluster{ 1436 Id: "c1", 1437 Name: "cluster1", 1438 }, 1439 State: vtadminpb.Tablet_SERVING, 1440 Tablet: &topodatapb.Tablet{ 1441 Alias: &topodatapb.TabletAlias{ 1442 Cell: "zone1", 1443 Uid: 100, 1444 }, 1445 Keyspace: "otherkeyspace", 1446 }, 1447 }, 1448 }, 1449 req: &vtadminpb.GetSchemaRequest{ 1450 ClusterId: "c1", 1451 Keyspace: "testkeyspace", 1452 Table: "testtable", 1453 }, 1454 expected: nil, 1455 shouldErr: true, 1456 }, 1457 { 1458 name: "no serving tablet found for keyspace", 1459 clusterID: 1, 1460 ts: memorytopo.NewServer("zone1"), 1461 tablets: []*vtadminpb.Tablet{ 1462 { 1463 Cluster: &vtadminpb.Cluster{ 1464 Id: "c1", 1465 Name: "cluster1", 1466 }, 1467 State: vtadminpb.Tablet_NOT_SERVING, 1468 Tablet: &topodatapb.Tablet{ 1469 Alias: &topodatapb.TabletAlias{ 1470 Cell: "zone1", 1471 Uid: 100, 1472 }, 1473 Keyspace: "testkeyspace", 1474 }, 1475 }, 1476 }, 1477 req: &vtadminpb.GetSchemaRequest{ 1478 ClusterId: "c1", 1479 Keyspace: "testkeyspace", 1480 Table: "testtable", 1481 }, 1482 expected: nil, 1483 shouldErr: true, 1484 }, 1485 { 1486 name: "error in GetSchema call", 1487 clusterID: 1, 1488 ts: memorytopo.NewServer("zone1"), 1489 tmc: &testutil.TabletManagerClient{ 1490 GetSchemaResults: map[string]struct { 1491 Schema *tabletmanagerdatapb.SchemaDefinition 1492 Error error 1493 }{ 1494 "zone1-0000000100": { 1495 Schema: &tabletmanagerdatapb.SchemaDefinition{ 1496 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1497 { 1498 Name: "testtable", 1499 }, 1500 { 1501 Name: "table2", 1502 }, 1503 { 1504 Name: "table3", 1505 }, 1506 }, 1507 }, 1508 Error: assert.AnError, 1509 }, 1510 }, 1511 }, 1512 tablets: []*vtadminpb.Tablet{ 1513 { 1514 Cluster: &vtadminpb.Cluster{ 1515 Id: "c1", 1516 Name: "cluster1", 1517 }, 1518 State: vtadminpb.Tablet_SERVING, 1519 Tablet: &topodatapb.Tablet{ 1520 Alias: &topodatapb.TabletAlias{ 1521 Cell: "zone1", 1522 Uid: 100, 1523 }, 1524 Keyspace: "testkeyspace", 1525 }, 1526 }, 1527 }, 1528 req: &vtadminpb.GetSchemaRequest{ 1529 ClusterId: "c1", 1530 Keyspace: "testkeyspace", 1531 Table: "testtable", 1532 }, 1533 expected: nil, 1534 shouldErr: true, 1535 }, 1536 } 1537 1538 ctx := context.Background() 1539 1540 for _, tt := range tests { 1541 tt := tt 1542 1543 t.Run(tt.name, func(t *testing.T) { 1544 t.Parallel() 1545 1546 vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer { 1547 return grpcvtctldserver.NewVtctldServer(ts) 1548 }) 1549 1550 testutil.AddTablets(ctx, t, tt.ts, nil, vtadmintestutil.TopodataTabletsFromVTAdminTablets(tt.tablets)...) 1551 1552 testutil.WithTestServer(t, vtctld, func(t *testing.T, client vtctldclient.VtctldClient) { 1553 c := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 1554 Cluster: &vtadminpb.Cluster{ 1555 Id: fmt.Sprintf("c%d", tt.clusterID), 1556 Name: fmt.Sprintf("cluster%d", tt.clusterID), 1557 }, 1558 VtctldClient: client, 1559 Tablets: tt.tablets, 1560 }) 1561 api := NewAPI([]*cluster.Cluster{c}, Options{}) 1562 defer api.Close() 1563 1564 resp, err := api.GetSchema(ctx, tt.req) 1565 if tt.shouldErr { 1566 assert.Error(t, err) 1567 1568 return 1569 } 1570 1571 if resp != nil { 1572 // Clone so our mutation below doesn't trip the race detector. 1573 resp = proto.Clone(resp).(*vtadminpb.Schema) 1574 } 1575 1576 assert.NoError(t, err) 1577 assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp) 1578 }) 1579 }) 1580 } 1581 1582 t.Run("size aggregation", func(t *testing.T) { 1583 t.Parallel() 1584 1585 c1pb := &vtadminpb.Cluster{ 1586 Id: "c1", 1587 Name: "cluster1", 1588 } 1589 c1 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 1590 Cluster: c1pb, 1591 VtctldClient: &fakevtctldclient.VtctldClient{ 1592 FindAllShardsInKeyspaceResults: map[string]struct { 1593 Response *vtctldatapb.FindAllShardsInKeyspaceResponse 1594 Error error 1595 }{ 1596 "testkeyspace": { 1597 Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{ 1598 Shards: map[string]*vtctldatapb.Shard{ 1599 "-80": { 1600 Keyspace: "testkeyspace", 1601 Name: "-80", 1602 Shard: &topodatapb.Shard{ 1603 IsPrimaryServing: true, 1604 PrimaryAlias: &topodatapb.TabletAlias{ 1605 Cell: "c1zone1", 1606 Uid: 100, 1607 }, 1608 }, 1609 }, 1610 "80-": { 1611 Keyspace: "testkeyspace", 1612 Name: "80-", 1613 Shard: &topodatapb.Shard{ 1614 IsPrimaryServing: true, 1615 PrimaryAlias: &topodatapb.TabletAlias{ 1616 Cell: "c1zone1", 1617 Uid: 200, 1618 }, 1619 }, 1620 }, 1621 }, 1622 }, 1623 }, 1624 }, 1625 GetSchemaResults: map[string]struct { 1626 Response *vtctldatapb.GetSchemaResponse 1627 Error error 1628 }{ 1629 "c1zone1-0000000100": { 1630 Response: &vtctldatapb.GetSchemaResponse{ 1631 Schema: &tabletmanagerdatapb.SchemaDefinition{ 1632 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1633 { 1634 Name: "testtable", 1635 RowCount: 10, 1636 DataLength: 100, 1637 }, 1638 }, 1639 }, 1640 }, 1641 }, 1642 "c1zone1-0000000200": { 1643 Response: &vtctldatapb.GetSchemaResponse{ 1644 Schema: &tabletmanagerdatapb.SchemaDefinition{ 1645 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1646 { 1647 Name: "testtable", 1648 RowCount: 20, 1649 DataLength: 200, 1650 }, 1651 }, 1652 }, 1653 }, 1654 }, 1655 }, 1656 }, 1657 Tablets: []*vtadminpb.Tablet{ 1658 { 1659 Cluster: c1pb, 1660 Tablet: &topodatapb.Tablet{ 1661 Alias: &topodatapb.TabletAlias{ 1662 Cell: "c1zone1", 1663 Uid: 100, 1664 }, 1665 Keyspace: "testkeyspace", 1666 Shard: "-80", 1667 }, 1668 State: vtadminpb.Tablet_SERVING, 1669 }, 1670 { 1671 Cluster: c1pb, 1672 Tablet: &topodatapb.Tablet{ 1673 Alias: &topodatapb.TabletAlias{ 1674 Cell: "c1zone1", 1675 Uid: 200, 1676 }, 1677 Keyspace: "testkeyspace", 1678 Shard: "80-", 1679 }, 1680 State: vtadminpb.Tablet_SERVING, 1681 }, 1682 }, 1683 }, 1684 ) 1685 c2 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 1686 Cluster: &vtadminpb.Cluster{ 1687 Id: "c2", 1688 Name: "cluster2", 1689 }, 1690 }, 1691 ) 1692 1693 api := NewAPI([]*cluster.Cluster{c1, c2}, Options{}) 1694 defer api.Close() 1695 1696 schema, err := api.GetSchema(ctx, &vtadminpb.GetSchemaRequest{ 1697 ClusterId: c1.ID, 1698 Keyspace: "testkeyspace", 1699 Table: "testtable", 1700 TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{ 1701 AggregateSizes: true, 1702 }, 1703 }) 1704 1705 expected := &vtadminpb.Schema{ 1706 Cluster: c1pb, 1707 Keyspace: "testkeyspace", 1708 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1709 { 1710 Name: "testtable", 1711 }, 1712 }, 1713 TableSizes: map[string]*vtadminpb.Schema_TableSize{ 1714 "testtable": { 1715 RowCount: 10 + 20, 1716 DataLength: 100 + 200, 1717 ByShard: map[string]*vtadminpb.Schema_ShardTableSize{ 1718 "-80": { 1719 RowCount: 10, 1720 DataLength: 100, 1721 }, 1722 "80-": { 1723 RowCount: 20, 1724 DataLength: 200, 1725 }, 1726 }, 1727 }, 1728 }, 1729 } 1730 1731 if schema != nil { 1732 // Clone so our mutation below doesn't trip the race detector. 1733 schema = proto.Clone(schema).(*vtadminpb.Schema) 1734 1735 for _, td := range schema.TableDefinitions { 1736 // Zero these out because they're non-deterministic and also not 1737 // relevant to the final result. 1738 td.RowCount = 0 1739 td.DataLength = 0 1740 } 1741 } 1742 1743 assert.NoError(t, err) 1744 assert.Truef(t, proto.Equal(expected, schema), "expected %v, got %v", expected, schema) 1745 }) 1746 } 1747 1748 func TestGetSchemas(t *testing.T) { 1749 t.Parallel() 1750 1751 tests := []struct { 1752 name string 1753 clusterTablets [][]*vtadminpb.Tablet 1754 // Indexed by tablet alias 1755 tabletSchemas map[string]*tabletmanagerdatapb.SchemaDefinition 1756 req *vtadminpb.GetSchemasRequest 1757 expected *vtadminpb.GetSchemasResponse 1758 }{ 1759 { 1760 name: "one schema in one cluster", 1761 clusterTablets: [][]*vtadminpb.Tablet{ 1762 // cluster0 1763 { 1764 { 1765 State: vtadminpb.Tablet_SERVING, 1766 Tablet: &topodatapb.Tablet{ 1767 Alias: &topodatapb.TabletAlias{ 1768 Cell: "c0_cell1", 1769 Uid: 100, 1770 }, 1771 Keyspace: "commerce", 1772 }, 1773 }, 1774 }, 1775 // cluster1 1776 { 1777 { 1778 State: vtadminpb.Tablet_SERVING, 1779 Tablet: &topodatapb.Tablet{ 1780 Alias: &topodatapb.TabletAlias{ 1781 Cell: "c1_cell1", 1782 Uid: 100, 1783 }, 1784 Keyspace: "commerce", 1785 }, 1786 }, 1787 }, 1788 }, 1789 tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{ 1790 "c0_cell1-0000000100": { 1791 DatabaseSchema: "CREATE DATABASE vt_testkeyspace", 1792 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1793 { 1794 Name: "t1", 1795 Schema: `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`, 1796 Type: "BASE", 1797 Columns: []string{"id"}, 1798 DataLength: 100, 1799 RowCount: 50, 1800 Fields: []*querypb.Field{ 1801 { 1802 Name: "id", 1803 Type: querypb.Type_INT32, 1804 }, 1805 }, 1806 }, 1807 }, 1808 }, 1809 }, 1810 req: &vtadminpb.GetSchemasRequest{}, 1811 expected: &vtadminpb.GetSchemasResponse{ 1812 Schemas: []*vtadminpb.Schema{ 1813 { 1814 Cluster: &vtadminpb.Cluster{ 1815 Id: "c0", 1816 Name: "cluster0", 1817 }, 1818 Keyspace: "commerce", 1819 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1820 { 1821 Name: "t1", 1822 Schema: `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`, 1823 Type: "BASE", 1824 Columns: []string{"id"}, 1825 DataLength: 100, 1826 RowCount: 50, 1827 Fields: []*querypb.Field{ 1828 { 1829 Name: "id", 1830 Type: querypb.Type_INT32, 1831 }, 1832 }, 1833 }, 1834 }, 1835 TableSizes: map[string]*vtadminpb.Schema_TableSize{}, 1836 }, 1837 }, 1838 }, 1839 }, 1840 { 1841 name: "one schema in each cluster", 1842 clusterTablets: [][]*vtadminpb.Tablet{ 1843 // cluster0 1844 { 1845 { 1846 State: vtadminpb.Tablet_SERVING, 1847 Tablet: &topodatapb.Tablet{ 1848 Alias: &topodatapb.TabletAlias{ 1849 Cell: "c0_cell1", 1850 Uid: 100, 1851 }, 1852 Keyspace: "commerce", 1853 }, 1854 }, 1855 }, 1856 // cluster1 1857 { 1858 { 1859 State: vtadminpb.Tablet_SERVING, 1860 Tablet: &topodatapb.Tablet{ 1861 Alias: &topodatapb.TabletAlias{ 1862 Cell: "c1_cell1", 1863 Uid: 100, 1864 }, 1865 Keyspace: "commerce", 1866 }, 1867 }, 1868 }, 1869 }, 1870 tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{ 1871 "c0_cell1-0000000100": { 1872 DatabaseSchema: "CREATE DATABASE vt_testkeyspace", 1873 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1874 { 1875 Name: "t1", 1876 Schema: `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`, 1877 Type: "BASE", 1878 Columns: []string{"id"}, 1879 DataLength: 100, 1880 RowCount: 50, 1881 Fields: []*querypb.Field{ 1882 { 1883 Name: "id", 1884 Type: querypb.Type_INT32, 1885 }, 1886 }, 1887 }, 1888 }, 1889 }, 1890 "c1_cell1-0000000100": { 1891 DatabaseSchema: "CREATE DATABASE vt_testkeyspace", 1892 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1893 { 1894 Name: "t2", 1895 Schema: `CREATE TABLE t2 (id int(11) not null,PRIMARY KEY (id));`, 1896 Type: "BASE", 1897 Columns: []string{"id"}, 1898 DataLength: 100, 1899 RowCount: 50, 1900 Fields: []*querypb.Field{ 1901 { 1902 Name: "id", 1903 Type: querypb.Type_INT32, 1904 }, 1905 }, 1906 }, 1907 }, 1908 }, 1909 }, 1910 req: &vtadminpb.GetSchemasRequest{}, 1911 expected: &vtadminpb.GetSchemasResponse{ 1912 Schemas: []*vtadminpb.Schema{ 1913 { 1914 Cluster: &vtadminpb.Cluster{ 1915 Id: "c0", 1916 Name: "cluster0", 1917 }, 1918 Keyspace: "commerce", 1919 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1920 { 1921 Name: "t1", 1922 Schema: `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`, 1923 Type: "BASE", 1924 Columns: []string{"id"}, 1925 DataLength: 100, 1926 RowCount: 50, 1927 Fields: []*querypb.Field{ 1928 { 1929 Name: "id", 1930 Type: querypb.Type_INT32, 1931 }, 1932 }, 1933 }, 1934 }, 1935 TableSizes: map[string]*vtadminpb.Schema_TableSize{}, 1936 }, 1937 { 1938 Cluster: &vtadminpb.Cluster{ 1939 Id: "c1", 1940 Name: "cluster1", 1941 }, 1942 Keyspace: "commerce", 1943 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1944 { 1945 Name: "t2", 1946 Schema: `CREATE TABLE t2 (id int(11) not null,PRIMARY KEY (id));`, 1947 Type: "BASE", 1948 Columns: []string{"id"}, 1949 DataLength: 100, 1950 RowCount: 50, 1951 Fields: []*querypb.Field{ 1952 { 1953 Name: "id", 1954 Type: querypb.Type_INT32, 1955 }, 1956 }, 1957 }, 1958 }, 1959 TableSizes: map[string]*vtadminpb.Schema_TableSize{}, 1960 }, 1961 }, 1962 }, 1963 }, 1964 { 1965 name: "filtered by cluster ID", 1966 clusterTablets: [][]*vtadminpb.Tablet{ 1967 // cluster0 1968 { 1969 { 1970 State: vtadminpb.Tablet_SERVING, 1971 Tablet: &topodatapb.Tablet{ 1972 Alias: &topodatapb.TabletAlias{ 1973 Cell: "c0_cell1", 1974 Uid: 100, 1975 }, 1976 Keyspace: "commerce", 1977 }, 1978 }, 1979 }, 1980 // cluster1 1981 { 1982 { 1983 State: vtadminpb.Tablet_SERVING, 1984 Tablet: &topodatapb.Tablet{ 1985 Alias: &topodatapb.TabletAlias{ 1986 Cell: "c1_cell1", 1987 Uid: 100, 1988 }, 1989 Keyspace: "commerce", 1990 }, 1991 }, 1992 }, 1993 }, 1994 tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{ 1995 "c0_cell1-0000000100": { 1996 DatabaseSchema: "CREATE DATABASE vt_testkeyspace", 1997 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 1998 { 1999 Name: "t1", 2000 Schema: `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`, 2001 Type: "BASE", 2002 Columns: []string{"id"}, 2003 DataLength: 100, 2004 RowCount: 50, 2005 Fields: []*querypb.Field{ 2006 { 2007 Name: "id", 2008 Type: querypb.Type_INT32, 2009 }, 2010 }, 2011 }, 2012 }, 2013 }, 2014 "c1_cell1-0000000100": { 2015 DatabaseSchema: "CREATE DATABASE vt_testkeyspace", 2016 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2017 { 2018 Name: "t2", 2019 Schema: `CREATE TABLE t2 (id int(11) not null,PRIMARY KEY (id));`, 2020 Type: "BASE", 2021 Columns: []string{"id"}, 2022 DataLength: 100, 2023 RowCount: 50, 2024 Fields: []*querypb.Field{ 2025 { 2026 Name: "id", 2027 Type: querypb.Type_INT32, 2028 }, 2029 }, 2030 }, 2031 }, 2032 }, 2033 }, 2034 req: &vtadminpb.GetSchemasRequest{ 2035 ClusterIds: []string{"c1"}, 2036 }, 2037 expected: &vtadminpb.GetSchemasResponse{ 2038 Schemas: []*vtadminpb.Schema{ 2039 { 2040 Cluster: &vtadminpb.Cluster{ 2041 Id: "c1", 2042 Name: "cluster1", 2043 }, 2044 Keyspace: "commerce", 2045 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2046 { 2047 Name: "t2", 2048 Schema: `CREATE TABLE t2 (id int(11) not null,PRIMARY KEY (id));`, 2049 Type: "BASE", 2050 Columns: []string{"id"}, 2051 DataLength: 100, 2052 RowCount: 50, 2053 Fields: []*querypb.Field{ 2054 { 2055 Name: "id", 2056 Type: querypb.Type_INT32, 2057 }, 2058 }, 2059 }, 2060 }, 2061 TableSizes: map[string]*vtadminpb.Schema_TableSize{}, 2062 }, 2063 }, 2064 }, 2065 }, 2066 { 2067 name: "filtered by cluster ID that doesn't exist", 2068 clusterTablets: [][]*vtadminpb.Tablet{ 2069 // cluster0 2070 { 2071 { 2072 State: vtadminpb.Tablet_SERVING, 2073 Tablet: &topodatapb.Tablet{ 2074 Alias: &topodatapb.TabletAlias{ 2075 Cell: "c0_cell1", 2076 Uid: 100, 2077 }, 2078 Keyspace: "commerce", 2079 }, 2080 }, 2081 }, 2082 }, 2083 tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{ 2084 "c0_cell1-0000000100": { 2085 DatabaseSchema: "CREATE DATABASE vt_testkeyspace", 2086 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2087 { 2088 Name: "t1", 2089 Schema: `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`, 2090 Type: "BASE", 2091 Columns: []string{"id"}, 2092 DataLength: 100, 2093 RowCount: 50, 2094 Fields: []*querypb.Field{ 2095 { 2096 Name: "id", 2097 Type: querypb.Type_INT32, 2098 }, 2099 }, 2100 }, 2101 }, 2102 }, 2103 }, 2104 req: &vtadminpb.GetSchemasRequest{ 2105 ClusterIds: []string{"nope"}, 2106 }, 2107 expected: &vtadminpb.GetSchemasResponse{ 2108 Schemas: nil, 2109 }, 2110 }, 2111 { 2112 name: "no schemas for any cluster", 2113 clusterTablets: [][]*vtadminpb.Tablet{ 2114 // cluster0 2115 { 2116 { 2117 State: vtadminpb.Tablet_SERVING, 2118 Tablet: &topodatapb.Tablet{ 2119 Alias: &topodatapb.TabletAlias{ 2120 Cell: "c0_cell1", 2121 Uid: 100, 2122 }, 2123 Keyspace: "commerce", 2124 }, 2125 }, 2126 }, 2127 }, 2128 tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{}, 2129 req: &vtadminpb.GetSchemasRequest{}, 2130 expected: &vtadminpb.GetSchemasResponse{ 2131 Schemas: nil, 2132 }, 2133 }, 2134 { 2135 name: "no serving tablets", 2136 clusterTablets: [][]*vtadminpb.Tablet{ 2137 // cluster0 2138 { 2139 { 2140 State: vtadminpb.Tablet_NOT_SERVING, 2141 Tablet: &topodatapb.Tablet{ 2142 Alias: &topodatapb.TabletAlias{ 2143 Cell: "c0_cell1", 2144 Uid: 100, 2145 }, 2146 Keyspace: "commerce", 2147 }, 2148 }, 2149 }, 2150 }, 2151 tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{ 2152 "c0_cell1-0000000100": { 2153 DatabaseSchema: "CREATE DATABASE vt_testkeyspace", 2154 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2155 { 2156 Name: "t1", 2157 Schema: `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`, 2158 Type: "BASE", 2159 Columns: []string{"id"}, 2160 DataLength: 100, 2161 RowCount: 50, 2162 Fields: []*querypb.Field{ 2163 { 2164 Name: "id", 2165 Type: querypb.Type_INT32, 2166 }, 2167 }, 2168 }, 2169 }, 2170 }, 2171 }, 2172 req: &vtadminpb.GetSchemasRequest{}, 2173 expected: &vtadminpb.GetSchemasResponse{ 2174 Schemas: nil, 2175 }, 2176 }, 2177 } 2178 2179 ctx := context.Background() 2180 2181 for _, tt := range tests { 2182 // Note that these test cases were written prior to the existence of 2183 // WithTestServers, so they are all written with the assumption that 2184 // there are exactly 2 clusters. 2185 tt := tt 2186 2187 t.Run(tt.name, func(t *testing.T) { 2188 t.Parallel() 2189 2190 topos := []*topo.Server{ 2191 memorytopo.NewServer("c0_cell1"), 2192 memorytopo.NewServer("c1_cell1"), 2193 } 2194 2195 tmc := testutil.TabletManagerClient{ 2196 GetSchemaResults: map[string]struct { 2197 Schema *tabletmanagerdatapb.SchemaDefinition 2198 Error error 2199 }{}, 2200 } 2201 2202 vtctlds := []vtctlservicepb.VtctldServer{ 2203 testutil.NewVtctldServerWithTabletManagerClient(t, topos[0], &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer { 2204 return grpcvtctldserver.NewVtctldServer(ts) 2205 }), 2206 testutil.NewVtctldServerWithTabletManagerClient(t, topos[1], &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer { 2207 return grpcvtctldserver.NewVtctldServer(ts) 2208 }), 2209 } 2210 2211 testutil.WithTestServers(t, func(t *testing.T, clients ...vtctldclient.VtctldClient) { 2212 clusters := make([]*cluster.Cluster, len(topos)) 2213 for cdx, toposerver := range topos { 2214 // Handle when a test doesn't define any tablets for a given cluster. 2215 var cts []*vtadminpb.Tablet 2216 if cdx < len(tt.clusterTablets) { 2217 cts = tt.clusterTablets[cdx] 2218 } 2219 2220 for _, tablet := range cts { 2221 // AddTablet also adds the keyspace + shard for us. 2222 testutil.AddTablet(ctx, t, toposerver, tablet.Tablet, nil) 2223 2224 // Adds each SchemaDefinition to the fake TabletManagerClient, or nil 2225 // if there are no schemas for that tablet. (All tablet aliases must 2226 // exist in the map. Otherwise, TabletManagerClient will return an error when 2227 // looking up the schema with tablet alias that doesn't exist.) 2228 alias := topoproto.TabletAliasString(tablet.Tablet.Alias) 2229 tmc.GetSchemaResults[alias] = struct { 2230 Schema *tabletmanagerdatapb.SchemaDefinition 2231 Error error 2232 }{ 2233 Schema: tt.tabletSchemas[alias], 2234 Error: nil, 2235 } 2236 } 2237 2238 clusters[cdx] = vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 2239 Cluster: &vtadminpb.Cluster{ 2240 Id: fmt.Sprintf("c%d", cdx), 2241 Name: fmt.Sprintf("cluster%d", cdx), 2242 }, 2243 VtctldClient: clients[cdx], 2244 Tablets: cts, 2245 }) 2246 } 2247 2248 api := NewAPI(clusters, Options{}) 2249 defer api.Close() 2250 2251 resp, err := api.GetSchemas(ctx, tt.req) 2252 require.NoError(t, err) 2253 2254 vtadmintestutil.AssertSchemaSlicesEqual(t, tt.expected.Schemas, resp.Schemas) 2255 }, vtctlds...) 2256 }) 2257 } 2258 2259 t.Run("size aggregation", func(t *testing.T) { 2260 t.Parallel() 2261 2262 c1pb := &vtadminpb.Cluster{ 2263 Id: "c1", 2264 Name: "cluster1", 2265 } 2266 c2pb := &vtadminpb.Cluster{ 2267 Id: "c2", 2268 Name: "cluster2", 2269 } 2270 2271 c1 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 2272 Cluster: c1pb, 2273 VtctldClient: &fakevtctldclient.VtctldClient{ 2274 FindAllShardsInKeyspaceResults: map[string]struct { 2275 Response *vtctldatapb.FindAllShardsInKeyspaceResponse 2276 Error error 2277 }{ 2278 "testkeyspace": { 2279 Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{ 2280 Shards: map[string]*vtctldatapb.Shard{ 2281 "-80": { 2282 Keyspace: "testkeyspace", 2283 Name: "-80", 2284 Shard: &topodatapb.Shard{ 2285 IsPrimaryServing: true, 2286 PrimaryAlias: &topodatapb.TabletAlias{ 2287 Cell: "c1zone1", 2288 Uid: 100, 2289 }, 2290 }, 2291 }, 2292 "80-": { 2293 Keyspace: "testkeyspace", 2294 Name: "80-", 2295 Shard: &topodatapb.Shard{ 2296 IsPrimaryServing: true, 2297 PrimaryAlias: &topodatapb.TabletAlias{ 2298 Cell: "c1zone1", 2299 Uid: 200, 2300 }, 2301 }, 2302 }, 2303 }, 2304 }, 2305 }, 2306 "ks1": { 2307 Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{ 2308 Shards: map[string]*vtctldatapb.Shard{ 2309 "-": { 2310 Keyspace: "ks1", 2311 Name: "-", 2312 Shard: &topodatapb.Shard{ 2313 IsPrimaryServing: true, 2314 PrimaryAlias: &topodatapb.TabletAlias{ 2315 Cell: "c1zone2", 2316 Uid: 100, 2317 }, 2318 }, 2319 }, 2320 }, 2321 }, 2322 }, 2323 }, 2324 GetKeyspacesResults: &struct { 2325 Keyspaces []*vtctldatapb.Keyspace 2326 Error error 2327 }{ 2328 Keyspaces: []*vtctldatapb.Keyspace{ 2329 {Name: "testkeyspace"}, 2330 {Name: "ks1"}, 2331 }, 2332 }, 2333 GetSchemaResults: map[string]struct { 2334 Response *vtctldatapb.GetSchemaResponse 2335 Error error 2336 }{ 2337 "c1zone1-0000000100": { 2338 Response: &vtctldatapb.GetSchemaResponse{ 2339 Schema: &tabletmanagerdatapb.SchemaDefinition{ 2340 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2341 { 2342 Name: "testtable", 2343 RowCount: 10, 2344 DataLength: 100, 2345 }, 2346 }, 2347 }, 2348 }, 2349 }, 2350 "c1zone1-0000000200": { 2351 Response: &vtctldatapb.GetSchemaResponse{ 2352 Schema: &tabletmanagerdatapb.SchemaDefinition{ 2353 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2354 { 2355 Name: "testtable", 2356 RowCount: 20, 2357 DataLength: 200, 2358 }, 2359 }, 2360 }, 2361 }, 2362 }, 2363 }, 2364 }, 2365 Tablets: []*vtadminpb.Tablet{ 2366 { 2367 Cluster: c1pb, 2368 Tablet: &topodatapb.Tablet{ 2369 Alias: &topodatapb.TabletAlias{ 2370 Cell: "c1zone1", 2371 Uid: 100, 2372 }, 2373 Keyspace: "testkeyspace", 2374 Shard: "-80", 2375 }, 2376 State: vtadminpb.Tablet_SERVING, 2377 }, 2378 { 2379 Cluster: c1pb, 2380 Tablet: &topodatapb.Tablet{ 2381 Alias: &topodatapb.TabletAlias{ 2382 Cell: "c1zone1", 2383 Uid: 200, 2384 }, 2385 Keyspace: "testkeyspace", 2386 Shard: "80-", 2387 }, 2388 State: vtadminpb.Tablet_SERVING, 2389 }, 2390 }, 2391 }, 2392 ) 2393 c2 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 2394 Cluster: c2pb, 2395 VtctldClient: &fakevtctldclient.VtctldClient{ 2396 FindAllShardsInKeyspaceResults: map[string]struct { 2397 Response *vtctldatapb.FindAllShardsInKeyspaceResponse 2398 Error error 2399 }{ 2400 "ks2": { 2401 Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{ 2402 Shards: map[string]*vtctldatapb.Shard{ 2403 "-": { 2404 Keyspace: "ks2", 2405 Name: "-", 2406 Shard: &topodatapb.Shard{ 2407 IsPrimaryServing: true, 2408 PrimaryAlias: &topodatapb.TabletAlias{ 2409 Cell: "c2z1", 2410 Uid: 100, 2411 }, 2412 }, 2413 }, 2414 }, 2415 }, 2416 }, 2417 }, 2418 GetKeyspacesResults: &struct { 2419 Keyspaces []*vtctldatapb.Keyspace 2420 Error error 2421 }{ 2422 Keyspaces: []*vtctldatapb.Keyspace{ 2423 { 2424 Name: "ks2", 2425 }, 2426 }, 2427 }, 2428 GetSchemaResults: map[string]struct { 2429 Response *vtctldatapb.GetSchemaResponse 2430 Error error 2431 }{ 2432 "c2z1-0000000100": { 2433 Response: &vtctldatapb.GetSchemaResponse{ 2434 Schema: &tabletmanagerdatapb.SchemaDefinition{ 2435 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2436 { 2437 Name: "t2", 2438 DataLength: 5, 2439 RowCount: 7, 2440 }, 2441 { 2442 Name: "_t2_ghc", 2443 DataLength: 5, 2444 RowCount: 7, 2445 }, 2446 }, 2447 }, 2448 }, 2449 }, 2450 }, 2451 }, 2452 Tablets: []*vtadminpb.Tablet{ 2453 { 2454 Cluster: c2pb, 2455 Tablet: &topodatapb.Tablet{ 2456 Alias: &topodatapb.TabletAlias{ 2457 Cell: "c2z1", 2458 Uid: 100, 2459 }, 2460 Keyspace: "ks2", 2461 Shard: "-", 2462 }, 2463 State: vtadminpb.Tablet_SERVING, 2464 }, 2465 }, 2466 }, 2467 ) 2468 2469 api := NewAPI([]*cluster.Cluster{c1, c2}, Options{}) 2470 defer api.Close() 2471 2472 resp, err := api.GetSchemas(ctx, &vtadminpb.GetSchemasRequest{ 2473 TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{ 2474 AggregateSizes: true, 2475 }, 2476 }) 2477 2478 expected := &vtadminpb.GetSchemasResponse{ 2479 Schemas: []*vtadminpb.Schema{ 2480 { 2481 Cluster: c1pb, 2482 Keyspace: "testkeyspace", 2483 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2484 { 2485 Name: "testtable", 2486 }, 2487 }, 2488 TableSizes: map[string]*vtadminpb.Schema_TableSize{ 2489 "testtable": { 2490 RowCount: 10 + 20, 2491 DataLength: 100 + 200, 2492 ByShard: map[string]*vtadminpb.Schema_ShardTableSize{ 2493 "-80": { 2494 RowCount: 10, 2495 DataLength: 100, 2496 }, 2497 "80-": { 2498 RowCount: 20, 2499 DataLength: 200, 2500 }, 2501 }, 2502 }, 2503 }, 2504 }, 2505 { 2506 Cluster: c2pb, 2507 Keyspace: "ks2", 2508 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 2509 {Name: "t2"}, 2510 {Name: "_t2_ghc"}, 2511 }, 2512 TableSizes: map[string]*vtadminpb.Schema_TableSize{ 2513 "t2": { 2514 DataLength: 5, 2515 RowCount: 7, 2516 ByShard: map[string]*vtadminpb.Schema_ShardTableSize{ 2517 "-": { 2518 DataLength: 5, 2519 RowCount: 7, 2520 }, 2521 }, 2522 }, 2523 "_t2_ghc": { 2524 DataLength: 5, 2525 RowCount: 7, 2526 ByShard: map[string]*vtadminpb.Schema_ShardTableSize{ 2527 "-": { 2528 DataLength: 5, 2529 RowCount: 7, 2530 }, 2531 }, 2532 }, 2533 }, 2534 }, 2535 }, 2536 } 2537 2538 if resp != nil { 2539 // Clone schemas so our mutations below don't trip the race detector. 2540 schemas := make([]*vtadminpb.Schema, len(resp.Schemas)) 2541 for i, schema := range resp.Schemas { 2542 schema := proto.Clone(schema).(*vtadminpb.Schema) 2543 2544 for _, td := range schema.TableDefinitions { 2545 // Zero these out because they're non-deterministic and also not 2546 // relevant to the final result. 2547 td.RowCount = 0 2548 td.DataLength = 0 2549 } 2550 2551 schemas[i] = schema 2552 } 2553 2554 resp.Schemas = schemas 2555 } 2556 2557 assert.NoError(t, err) 2558 assert.Truef(t, proto.Equal(expected, resp), "expected: %v, got: %v", expected, resp) 2559 }) 2560 } 2561 2562 func TestGetSrvVSchema(t *testing.T) { 2563 t.Parallel() 2564 2565 clusterID := "c0" 2566 clusterName := "cluster0" 2567 2568 tests := []struct { 2569 name string 2570 cells []string 2571 2572 // tt.cellSrvVSchemas maps cell name to the SrvVSchema for that cell. 2573 // Not all cells in tt.cells necessarily map to a SrvVSchema. 2574 cellSrvVSchemas map[string]*vschemapb.SrvVSchema 2575 srvVSchema *vschemapb.SrvVSchema 2576 req *vtadminpb.GetSrvVSchemaRequest 2577 expected *vtadminpb.SrvVSchema 2578 shouldErr bool 2579 }{ 2580 { 2581 name: "success", 2582 cells: []string{"zone0"}, 2583 cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{ 2584 "zone0": { 2585 Keyspaces: map[string]*vschemapb.Keyspace{ 2586 "commerce": { 2587 Tables: map[string]*vschemapb.Table{ 2588 "customer": {}, 2589 }, 2590 }, 2591 "customer": { 2592 Tables: map[string]*vschemapb.Table{ 2593 "customer": {}, 2594 }, 2595 }, 2596 }, 2597 RoutingRules: &vschemapb.RoutingRules{ 2598 Rules: []*vschemapb.RoutingRule{ 2599 { 2600 FromTable: "customer", 2601 ToTables: []string{"commerce.customer"}, 2602 }, 2603 { 2604 FromTable: "customer@rdonly", 2605 ToTables: []string{"customer.customer"}, 2606 }, 2607 { 2608 FromTable: "customer.customer", 2609 ToTables: []string{"commerce.customer"}, 2610 }, 2611 }, 2612 }, 2613 }, 2614 }, 2615 req: &vtadminpb.GetSrvVSchemaRequest{ 2616 Cell: "zone0", 2617 ClusterId: clusterID, 2618 }, 2619 expected: &vtadminpb.SrvVSchema{ 2620 Cell: "zone0", 2621 Cluster: &vtadminpb.Cluster{ 2622 Id: clusterID, 2623 Name: clusterName, 2624 }, 2625 SrvVSchema: &vschemapb.SrvVSchema{ 2626 Keyspaces: map[string]*vschemapb.Keyspace{ 2627 "commerce": { 2628 Tables: map[string]*vschemapb.Table{ 2629 "customer": {}, 2630 }, 2631 }, 2632 "customer": { 2633 Tables: map[string]*vschemapb.Table{ 2634 "customer": {}, 2635 }, 2636 }, 2637 }, 2638 RoutingRules: &vschemapb.RoutingRules{ 2639 Rules: []*vschemapb.RoutingRule{ 2640 { 2641 FromTable: "customer", 2642 ToTables: []string{"commerce.customer"}, 2643 }, 2644 { 2645 FromTable: "customer@rdonly", 2646 ToTables: []string{"customer.customer"}, 2647 }, 2648 { 2649 FromTable: "customer.customer", 2650 ToTables: []string{"commerce.customer"}, 2651 }, 2652 }, 2653 }, 2654 }, 2655 }, 2656 }, 2657 { 2658 name: "cluster doesn't exist", 2659 srvVSchema: nil, 2660 req: &vtadminpb.GetSrvVSchemaRequest{ 2661 Cell: "doesnt-matter", 2662 ClusterId: "doesnt-exist", 2663 }, 2664 shouldErr: true, 2665 }, 2666 { 2667 2668 name: "cell doesn't exist", 2669 srvVSchema: nil, 2670 req: &vtadminpb.GetSrvVSchemaRequest{ 2671 Cell: "doesnt-exist", 2672 ClusterId: clusterID, 2673 }, 2674 shouldErr: true, 2675 }, 2676 } 2677 2678 ctx := context.Background() 2679 2680 for _, tt := range tests { 2681 tt := tt 2682 2683 t.Run(tt.name, func(t *testing.T) { 2684 t.Parallel() 2685 2686 tmc := testutil.TabletManagerClient{} 2687 2688 toposerver := memorytopo.NewServer(tt.cells...) 2689 2690 vtctldserver := testutil.NewVtctldServerWithTabletManagerClient(t, toposerver, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer { 2691 return grpcvtctldserver.NewVtctldServer(ts) 2692 }) 2693 2694 testutil.WithTestServer(t, vtctldserver, func(t *testing.T, vtctldClient vtctldclient.VtctldClient) { 2695 for cell, svs := range tt.cellSrvVSchemas { 2696 err := toposerver.UpdateSrvVSchema(ctx, cell, svs) 2697 require.NoError(t, err) 2698 } 2699 2700 clusters := []*cluster.Cluster{ 2701 vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 2702 Cluster: &vtadminpb.Cluster{ 2703 Id: clusterID, 2704 Name: clusterName, 2705 }, 2706 VtctldClient: vtctldClient, 2707 }), 2708 } 2709 2710 api := NewAPI(clusters, Options{}) 2711 resp, err := api.GetSrvVSchema(ctx, tt.req) 2712 2713 if tt.shouldErr { 2714 assert.Error(t, err) 2715 return 2716 } 2717 2718 require.NoError(t, err) 2719 assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp) 2720 }) 2721 }) 2722 } 2723 } 2724 2725 func TestGetSrvVSchemas(t *testing.T) { 2726 t.Parallel() 2727 2728 clusterID := "c0" 2729 clusterName := "cluster0" 2730 2731 tests := []struct { 2732 name string 2733 cells []string 2734 // tt.cellSrvVSchemas maps cell name to the SrvVSchema for that cell. 2735 // Not all cells in tt.cells necessarily map to a SrvVSchema. 2736 cellSrvVSchemas map[string]*vschemapb.SrvVSchema 2737 req *vtadminpb.GetSrvVSchemasRequest 2738 expected *vtadminpb.GetSrvVSchemasResponse 2739 shouldErr bool 2740 }{ 2741 { 2742 name: "returns all cells", 2743 cells: []string{"zone0", "zone1"}, 2744 cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{ 2745 "zone0": { 2746 Keyspaces: map[string]*vschemapb.Keyspace{ 2747 "commerce": { 2748 Tables: map[string]*vschemapb.Table{ 2749 "customer": {}, 2750 }, 2751 }, 2752 "customer": { 2753 Tables: map[string]*vschemapb.Table{ 2754 "customer": {}, 2755 }, 2756 }, 2757 }, 2758 RoutingRules: &vschemapb.RoutingRules{ 2759 Rules: []*vschemapb.RoutingRule{ 2760 { 2761 FromTable: "customer", 2762 ToTables: []string{"commerce.customer"}, 2763 }, 2764 { 2765 FromTable: "customer@rdonly", 2766 ToTables: []string{"customer.customer"}, 2767 }, 2768 { 2769 FromTable: "customer.customer", 2770 ToTables: []string{"commerce.customer"}, 2771 }, 2772 }, 2773 }, 2774 }, 2775 "zone1": { 2776 Keyspaces: map[string]*vschemapb.Keyspace{}, 2777 RoutingRules: &vschemapb.RoutingRules{}, 2778 }, 2779 }, 2780 req: &vtadminpb.GetSrvVSchemasRequest{}, 2781 expected: &vtadminpb.GetSrvVSchemasResponse{ 2782 SrvVSchemas: []*vtadminpb.SrvVSchema{ 2783 { 2784 Cell: "zone0", 2785 Cluster: &vtadminpb.Cluster{ 2786 Id: clusterID, 2787 Name: clusterName, 2788 }, 2789 SrvVSchema: &vschemapb.SrvVSchema{ 2790 Keyspaces: map[string]*vschemapb.Keyspace{ 2791 "commerce": { 2792 Tables: map[string]*vschemapb.Table{ 2793 "customer": {}, 2794 }, 2795 }, 2796 "customer": { 2797 Tables: map[string]*vschemapb.Table{ 2798 "customer": {}, 2799 }, 2800 }, 2801 }, 2802 RoutingRules: &vschemapb.RoutingRules{ 2803 Rules: []*vschemapb.RoutingRule{ 2804 { 2805 FromTable: "customer", 2806 ToTables: []string{"commerce.customer"}, 2807 }, 2808 { 2809 FromTable: "customer@rdonly", 2810 ToTables: []string{"customer.customer"}, 2811 }, 2812 { 2813 FromTable: "customer.customer", 2814 ToTables: []string{"commerce.customer"}, 2815 }, 2816 }, 2817 }, 2818 }, 2819 }, 2820 { 2821 Cell: "zone1", 2822 Cluster: &vtadminpb.Cluster{ 2823 Id: clusterID, 2824 Name: clusterName, 2825 }, 2826 SrvVSchema: &vschemapb.SrvVSchema{ 2827 RoutingRules: &vschemapb.RoutingRules{}, 2828 }, 2829 }, 2830 }, 2831 }, 2832 }, 2833 { 2834 name: "filtering by cell", 2835 cells: []string{"zone0", "zone1"}, 2836 cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{ 2837 "zone0": { 2838 Keyspaces: map[string]*vschemapb.Keyspace{ 2839 "commerce": { 2840 Tables: map[string]*vschemapb.Table{ 2841 "customer": {}, 2842 }, 2843 }, 2844 "customer": { 2845 Tables: map[string]*vschemapb.Table{ 2846 "customer": {}, 2847 }, 2848 }, 2849 }, 2850 RoutingRules: &vschemapb.RoutingRules{ 2851 Rules: []*vschemapb.RoutingRule{ 2852 { 2853 FromTable: "customer", 2854 ToTables: []string{"commerce.customer"}, 2855 }, 2856 { 2857 FromTable: "customer@rdonly", 2858 ToTables: []string{"customer.customer"}, 2859 }, 2860 { 2861 FromTable: "customer.customer", 2862 ToTables: []string{"commerce.customer"}, 2863 }, 2864 }, 2865 }, 2866 }, 2867 "zone1": { 2868 Keyspaces: map[string]*vschemapb.Keyspace{}, 2869 RoutingRules: &vschemapb.RoutingRules{}, 2870 }, 2871 }, 2872 req: &vtadminpb.GetSrvVSchemasRequest{ 2873 Cells: []string{"zone1"}, 2874 }, 2875 expected: &vtadminpb.GetSrvVSchemasResponse{ 2876 SrvVSchemas: []*vtadminpb.SrvVSchema{ 2877 { 2878 Cell: "zone1", 2879 Cluster: &vtadminpb.Cluster{ 2880 Id: clusterID, 2881 Name: clusterName, 2882 }, 2883 SrvVSchema: &vschemapb.SrvVSchema{ 2884 RoutingRules: &vschemapb.RoutingRules{}, 2885 }, 2886 }, 2887 }, 2888 }, 2889 }, 2890 { 2891 name: "filtering by nonexistent cell", 2892 cells: []string{"zone0"}, 2893 cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{ 2894 "zone0": { 2895 Keyspaces: map[string]*vschemapb.Keyspace{}, 2896 RoutingRules: &vschemapb.RoutingRules{}, 2897 }, 2898 }, 2899 req: &vtadminpb.GetSrvVSchemasRequest{ 2900 Cells: []string{"doesnt-exist"}, 2901 }, 2902 expected: &vtadminpb.GetSrvVSchemasResponse{}, 2903 }, 2904 { 2905 name: "filtering with nonexistent cell", 2906 cells: []string{"zone0"}, 2907 cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{ 2908 "zone0": { 2909 Keyspaces: map[string]*vschemapb.Keyspace{}, 2910 RoutingRules: &vschemapb.RoutingRules{}, 2911 }, 2912 }, 2913 req: &vtadminpb.GetSrvVSchemasRequest{ 2914 Cells: []string{"doesnt-exist", "zone0"}, 2915 }, 2916 expected: &vtadminpb.GetSrvVSchemasResponse{ 2917 SrvVSchemas: []*vtadminpb.SrvVSchema{ 2918 { 2919 Cell: "zone0", 2920 Cluster: &vtadminpb.Cluster{ 2921 Id: clusterID, 2922 Name: clusterName, 2923 }, 2924 SrvVSchema: &vschemapb.SrvVSchema{ 2925 RoutingRules: &vschemapb.RoutingRules{}, 2926 }, 2927 }, 2928 }, 2929 }, 2930 }, 2931 { 2932 name: "existing cell without SrvVSchema", 2933 cells: []string{"zone0", "zone1"}, 2934 cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{ 2935 "zone0": { 2936 Keyspaces: map[string]*vschemapb.Keyspace{}, 2937 RoutingRules: &vschemapb.RoutingRules{}, 2938 }, 2939 }, 2940 req: &vtadminpb.GetSrvVSchemasRequest{ 2941 Cells: []string{"zone1"}, 2942 }, 2943 expected: &vtadminpb.GetSrvVSchemasResponse{ 2944 SrvVSchemas: []*vtadminpb.SrvVSchema{ 2945 { 2946 Cell: "zone1", 2947 Cluster: &vtadminpb.Cluster{ 2948 Id: clusterID, 2949 Name: clusterName, 2950 }, 2951 SrvVSchema: &vschemapb.SrvVSchema{}, 2952 }, 2953 }, 2954 }, 2955 }, 2956 { 2957 name: "filtering by nonexistent cluster", 2958 cells: []string{"zone0"}, 2959 cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{ 2960 "zone0": { 2961 Keyspaces: map[string]*vschemapb.Keyspace{}, 2962 RoutingRules: &vschemapb.RoutingRules{}, 2963 }, 2964 }, 2965 req: &vtadminpb.GetSrvVSchemasRequest{ 2966 ClusterIds: []string{"doesnt-exist"}, 2967 }, 2968 expected: &vtadminpb.GetSrvVSchemasResponse{}, 2969 }, 2970 } 2971 2972 ctx := context.Background() 2973 2974 for _, tt := range tests { 2975 tt := tt 2976 2977 t.Run(tt.name, func(t *testing.T) { 2978 t.Parallel() 2979 2980 tmc := testutil.TabletManagerClient{} 2981 2982 toposerver := memorytopo.NewServer(tt.cells...) 2983 2984 vtctldserver := testutil.NewVtctldServerWithTabletManagerClient(t, toposerver, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer { 2985 return grpcvtctldserver.NewVtctldServer(ts) 2986 }) 2987 2988 testutil.WithTestServer(t, vtctldserver, func(t *testing.T, vtctldClient vtctldclient.VtctldClient) { 2989 for cell, svs := range tt.cellSrvVSchemas { 2990 err := toposerver.UpdateSrvVSchema(ctx, cell, svs) 2991 require.NoError(t, err) 2992 } 2993 2994 clusters := []*cluster.Cluster{ 2995 vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 2996 Cluster: &vtadminpb.Cluster{ 2997 Id: clusterID, 2998 Name: clusterName, 2999 }, 3000 VtctldClient: vtctldClient, 3001 }), 3002 } 3003 3004 api := NewAPI(clusters, Options{}) 3005 resp, err := api.GetSrvVSchemas(ctx, tt.req) 3006 3007 if tt.shouldErr { 3008 assert.Error(t, err) 3009 return 3010 } 3011 3012 require.NoError(t, err) 3013 vtadmintestutil.AssertSrvVSchemaSlicesEqual(t, tt.expected.SrvVSchemas, resp.SrvVSchemas) 3014 }) 3015 }) 3016 } 3017 } 3018 3019 func TestGetTablet(t *testing.T) { 3020 t.Parallel() 3021 3022 tests := []struct { 3023 name string 3024 clusterTablets [][]*vtadminpb.Tablet 3025 dbconfigs map[string]vtadmintestutil.Dbcfg 3026 req *vtadminpb.GetTabletRequest 3027 expected *vtadminpb.Tablet 3028 shouldErr bool 3029 }{ 3030 { 3031 name: "single cluster", 3032 clusterTablets: [][]*vtadminpb.Tablet{ 3033 { 3034 /* cluster 0 */ 3035 { 3036 State: vtadminpb.Tablet_SERVING, 3037 Tablet: &topodatapb.Tablet{ 3038 Alias: &topodatapb.TabletAlias{ 3039 Uid: 100, 3040 Cell: "zone1", 3041 }, 3042 Hostname: "ks1-00-00-zone1-a", 3043 Keyspace: "ks1", 3044 Shard: "-", 3045 Type: topodatapb.TabletType_PRIMARY, 3046 }, 3047 }, 3048 }, 3049 }, 3050 dbconfigs: map[string]vtadmintestutil.Dbcfg{}, 3051 req: &vtadminpb.GetTabletRequest{ 3052 Alias: &topodatapb.TabletAlias{ 3053 Cell: "zone1", 3054 Uid: 100, 3055 }, 3056 }, 3057 expected: &vtadminpb.Tablet{ 3058 Cluster: &vtadminpb.Cluster{ 3059 Id: "c0", 3060 Name: "cluster0", 3061 }, 3062 State: vtadminpb.Tablet_SERVING, 3063 Tablet: &topodatapb.Tablet{ 3064 Alias: &topodatapb.TabletAlias{ 3065 Uid: 100, 3066 Cell: "zone1", 3067 }, 3068 Hostname: "ks1-00-00-zone1-a", 3069 Keyspace: "ks1", 3070 Shard: "-", 3071 Type: topodatapb.TabletType_PRIMARY, 3072 }, 3073 }, 3074 shouldErr: false, 3075 }, 3076 { 3077 name: "one cluster errors", 3078 clusterTablets: [][]*vtadminpb.Tablet{ 3079 /* cluster 0 */ 3080 { 3081 { 3082 State: vtadminpb.Tablet_SERVING, 3083 Tablet: &topodatapb.Tablet{ 3084 Alias: &topodatapb.TabletAlias{ 3085 Uid: 100, 3086 Cell: "zone1", 3087 }, 3088 Hostname: "ks1-00-00-zone1-a", 3089 Keyspace: "ks1", 3090 Shard: "-", 3091 Type: topodatapb.TabletType_PRIMARY, 3092 }, 3093 }, 3094 }, 3095 /* cluster 1 */ 3096 { 3097 { 3098 State: vtadminpb.Tablet_SERVING, 3099 Tablet: &topodatapb.Tablet{ 3100 Alias: &topodatapb.TabletAlias{ 3101 Uid: 200, 3102 Cell: "zone1", 3103 }, 3104 Hostname: "ks2-00-00-zone1-a", 3105 Keyspace: "ks2", 3106 Shard: "-", 3107 Type: topodatapb.TabletType_PRIMARY, 3108 }, 3109 }, 3110 }, 3111 }, 3112 dbconfigs: map[string]vtadmintestutil.Dbcfg{ 3113 "c1": {ShouldErr: true}, 3114 }, 3115 req: &vtadminpb.GetTabletRequest{ 3116 Alias: &topodatapb.TabletAlias{ 3117 Cell: "doesntmatter", 3118 Uid: 100, 3119 }, 3120 }, 3121 expected: nil, 3122 shouldErr: true, 3123 }, 3124 { 3125 name: "multi cluster, selecting one with tablet", 3126 clusterTablets: [][]*vtadminpb.Tablet{ 3127 /* cluster 0 */ 3128 { 3129 { 3130 State: vtadminpb.Tablet_SERVING, 3131 Tablet: &topodatapb.Tablet{ 3132 Alias: &topodatapb.TabletAlias{ 3133 Uid: 100, 3134 Cell: "zone1", 3135 }, 3136 Hostname: "ks1-00-00-zone1-a", 3137 Keyspace: "ks1", 3138 Shard: "-", 3139 Type: topodatapb.TabletType_PRIMARY, 3140 }, 3141 }, 3142 }, 3143 /* cluster 1 */ 3144 { 3145 { 3146 State: vtadminpb.Tablet_SERVING, 3147 Tablet: &topodatapb.Tablet{ 3148 Alias: &topodatapb.TabletAlias{ 3149 Uid: 200, 3150 Cell: "zone1", 3151 }, 3152 Hostname: "ks2-00-00-zone1-a", 3153 Keyspace: "ks2", 3154 Shard: "-", 3155 Type: topodatapb.TabletType_PRIMARY, 3156 }, 3157 }, 3158 }, 3159 }, 3160 dbconfigs: map[string]vtadmintestutil.Dbcfg{}, 3161 req: &vtadminpb.GetTabletRequest{ 3162 Alias: &topodatapb.TabletAlias{ 3163 Cell: "zone1", 3164 Uid: 100, 3165 }, 3166 ClusterIds: []string{"c0"}, 3167 }, 3168 expected: &vtadminpb.Tablet{ 3169 Cluster: &vtadminpb.Cluster{ 3170 Id: "c0", 3171 Name: "cluster0", 3172 }, 3173 State: vtadminpb.Tablet_SERVING, 3174 Tablet: &topodatapb.Tablet{ 3175 Alias: &topodatapb.TabletAlias{ 3176 Uid: 100, 3177 Cell: "zone1", 3178 }, 3179 Hostname: "ks1-00-00-zone1-a", 3180 Keyspace: "ks1", 3181 Shard: "-", 3182 Type: topodatapb.TabletType_PRIMARY, 3183 }, 3184 }, 3185 shouldErr: false, 3186 }, 3187 { 3188 name: "multi cluster, multiple results", 3189 clusterTablets: [][]*vtadminpb.Tablet{ 3190 /* cluster 0 */ 3191 { 3192 { 3193 State: vtadminpb.Tablet_SERVING, 3194 Tablet: &topodatapb.Tablet{ 3195 Alias: &topodatapb.TabletAlias{ 3196 Uid: 100, 3197 Cell: "zone1", 3198 }, 3199 Hostname: "ks1-00-00-zone1-a", 3200 Keyspace: "ks1", 3201 Shard: "-", 3202 Type: topodatapb.TabletType_PRIMARY, 3203 }, 3204 }, 3205 }, 3206 /* cluster 1 */ 3207 { 3208 { 3209 State: vtadminpb.Tablet_SERVING, 3210 Tablet: &topodatapb.Tablet{ 3211 Alias: &topodatapb.TabletAlias{ 3212 Uid: 100, 3213 Cell: "zone1", 3214 }, 3215 Hostname: "ks1-00-00-zone1-a", 3216 Keyspace: "ks1", 3217 Shard: "-", 3218 Type: topodatapb.TabletType_PRIMARY, 3219 }, 3220 }, 3221 }, 3222 }, 3223 dbconfigs: map[string]vtadmintestutil.Dbcfg{}, 3224 req: &vtadminpb.GetTabletRequest{ 3225 Alias: &topodatapb.TabletAlias{ 3226 Cell: "zone1", 3227 Uid: 100, 3228 }, 3229 }, 3230 expected: nil, 3231 shouldErr: true, 3232 }, 3233 { 3234 name: "no results", 3235 clusterTablets: [][]*vtadminpb.Tablet{ 3236 /* cluster 0 */ 3237 {}, 3238 }, 3239 dbconfigs: map[string]vtadmintestutil.Dbcfg{}, 3240 req: &vtadminpb.GetTabletRequest{ 3241 Alias: &topodatapb.TabletAlias{ 3242 Cell: "zone1", 3243 Uid: 100, 3244 }, 3245 }, 3246 expected: nil, 3247 shouldErr: true, 3248 }, 3249 } 3250 3251 ctx := context.Background() 3252 3253 for _, tt := range tests { 3254 tt := tt 3255 3256 t.Run(tt.name, func(t *testing.T) { 3257 t.Parallel() 3258 3259 clusters := make([]*cluster.Cluster, len(tt.clusterTablets)) 3260 3261 for i, tablets := range tt.clusterTablets { 3262 cid := fmt.Sprintf("c%d", i) 3263 dbconfigs := tt.dbconfigs[cid] 3264 3265 clusters[i] = vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 3266 Cluster: &vtadminpb.Cluster{ 3267 Id: cid, 3268 Name: fmt.Sprintf("cluster%d", i), 3269 }, 3270 Tablets: tablets, 3271 DBConfig: dbconfigs, 3272 }) 3273 } 3274 3275 api := NewAPI(clusters, Options{}) 3276 resp, err := api.GetTablet(ctx, tt.req) 3277 if tt.shouldErr { 3278 assert.Error(t, err) 3279 return 3280 } 3281 3282 assert.NoError(t, err) 3283 utils.MustMatch(t, tt.expected, resp) 3284 }) 3285 } 3286 } 3287 3288 func TestGetTablets(t *testing.T) { 3289 t.Parallel() 3290 3291 tests := []struct { 3292 name string 3293 clusterTablets [][]*vtadminpb.Tablet 3294 dbconfigs map[string]vtadmintestutil.Dbcfg 3295 req *vtadminpb.GetTabletsRequest 3296 expected []*vtadminpb.Tablet 3297 shouldErr bool 3298 }{ 3299 { 3300 name: "single cluster", 3301 clusterTablets: [][]*vtadminpb.Tablet{ 3302 { 3303 /* cluster 0 */ 3304 { 3305 State: vtadminpb.Tablet_SERVING, 3306 Tablet: &topodatapb.Tablet{ 3307 Alias: &topodatapb.TabletAlias{ 3308 Uid: 100, 3309 Cell: "zone1", 3310 }, 3311 Hostname: "ks1-00-00-zone1-a", 3312 Keyspace: "ks1", 3313 Shard: "-", 3314 Type: topodatapb.TabletType_PRIMARY, 3315 }, 3316 }, 3317 }, 3318 }, 3319 dbconfigs: map[string]vtadmintestutil.Dbcfg{}, 3320 req: &vtadminpb.GetTabletsRequest{}, 3321 expected: []*vtadminpb.Tablet{ 3322 { 3323 Cluster: &vtadminpb.Cluster{ 3324 Id: "c0", 3325 Name: "cluster0", 3326 }, 3327 State: vtadminpb.Tablet_SERVING, 3328 Tablet: &topodatapb.Tablet{ 3329 Alias: &topodatapb.TabletAlias{ 3330 Uid: 100, 3331 Cell: "zone1", 3332 }, 3333 Hostname: "ks1-00-00-zone1-a", 3334 Keyspace: "ks1", 3335 Shard: "-", 3336 Type: topodatapb.TabletType_PRIMARY, 3337 }, 3338 }, 3339 }, 3340 shouldErr: false, 3341 }, 3342 { 3343 name: "one cluster errors", 3344 clusterTablets: [][]*vtadminpb.Tablet{ 3345 /* cluster 0 */ 3346 { 3347 { 3348 State: vtadminpb.Tablet_SERVING, 3349 Tablet: &topodatapb.Tablet{ 3350 Alias: &topodatapb.TabletAlias{ 3351 Uid: 100, 3352 Cell: "zone1", 3353 }, 3354 Hostname: "ks1-00-00-zone1-a", 3355 Keyspace: "ks1", 3356 Shard: "-", 3357 Type: topodatapb.TabletType_PRIMARY, 3358 }, 3359 }, 3360 }, 3361 /* cluster 1 */ 3362 { 3363 { 3364 State: vtadminpb.Tablet_SERVING, 3365 Tablet: &topodatapb.Tablet{ 3366 Alias: &topodatapb.TabletAlias{ 3367 Uid: 200, 3368 Cell: "zone1", 3369 }, 3370 Hostname: "ks2-00-00-zone1-a", 3371 Keyspace: "ks2", 3372 Shard: "-", 3373 Type: topodatapb.TabletType_PRIMARY, 3374 }, 3375 }, 3376 }, 3377 }, 3378 dbconfigs: map[string]vtadmintestutil.Dbcfg{ 3379 "c1": {ShouldErr: true}, 3380 }, 3381 req: &vtadminpb.GetTabletsRequest{}, 3382 expected: nil, 3383 shouldErr: true, 3384 }, 3385 { 3386 name: "multi cluster, selecting one", 3387 clusterTablets: [][]*vtadminpb.Tablet{ 3388 /* cluster 0 */ 3389 { 3390 { 3391 State: vtadminpb.Tablet_SERVING, 3392 Tablet: &topodatapb.Tablet{ 3393 Alias: &topodatapb.TabletAlias{ 3394 Uid: 100, 3395 Cell: "zone1", 3396 }, 3397 Hostname: "ks1-00-00-zone1-a", 3398 Keyspace: "ks1", 3399 Shard: "-", 3400 Type: topodatapb.TabletType_PRIMARY, 3401 }, 3402 }, 3403 }, 3404 /* cluster 1 */ 3405 { 3406 { 3407 State: vtadminpb.Tablet_SERVING, 3408 Tablet: &topodatapb.Tablet{ 3409 Alias: &topodatapb.TabletAlias{ 3410 Uid: 200, 3411 Cell: "zone1", 3412 }, 3413 Hostname: "ks2-00-00-zone1-a", 3414 Keyspace: "ks2", 3415 Shard: "-", 3416 Type: topodatapb.TabletType_PRIMARY, 3417 }, 3418 }, 3419 }, 3420 }, 3421 dbconfigs: map[string]vtadmintestutil.Dbcfg{}, 3422 req: &vtadminpb.GetTabletsRequest{ClusterIds: []string{"c0"}}, 3423 expected: []*vtadminpb.Tablet{ 3424 { 3425 Cluster: &vtadminpb.Cluster{ 3426 Id: "c0", 3427 Name: "cluster0", 3428 }, 3429 State: vtadminpb.Tablet_SERVING, 3430 Tablet: &topodatapb.Tablet{ 3431 Alias: &topodatapb.TabletAlias{ 3432 Uid: 100, 3433 Cell: "zone1", 3434 }, 3435 Hostname: "ks1-00-00-zone1-a", 3436 Keyspace: "ks1", 3437 Shard: "-", 3438 Type: topodatapb.TabletType_PRIMARY, 3439 }, 3440 }, 3441 }, 3442 shouldErr: false, 3443 }, 3444 } 3445 3446 ctx := context.Background() 3447 3448 for _, tt := range tests { 3449 tt := tt 3450 3451 t.Run(tt.name, func(t *testing.T) { 3452 t.Parallel() 3453 3454 clusters := make([]*cluster.Cluster, len(tt.clusterTablets)) 3455 3456 for i, tablets := range tt.clusterTablets { 3457 cid := fmt.Sprintf("c%d", i) 3458 dbconfigs := tt.dbconfigs[cid] 3459 3460 clusters[i] = vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 3461 Cluster: &vtadminpb.Cluster{ 3462 Id: cid, 3463 Name: fmt.Sprintf("cluster%d", i), 3464 }, 3465 Tablets: tablets, 3466 DBConfig: dbconfigs, 3467 }) 3468 } 3469 3470 api := NewAPI(clusters, Options{}) 3471 resp, err := api.GetTablets(ctx, tt.req) 3472 if tt.shouldErr { 3473 assert.Error(t, err) 3474 return 3475 } 3476 3477 assert.NoError(t, err) 3478 assert.ElementsMatch(t, tt.expected, resp.Tablets) 3479 }) 3480 } 3481 } 3482 3483 func TestGetVSchema(t *testing.T) { 3484 t.Parallel() 3485 3486 tests := []struct { 3487 name string 3488 clusterCfg vtadmintestutil.TestClusterConfig 3489 req *vtadminpb.GetVSchemaRequest 3490 expected *vtadminpb.VSchema 3491 shouldErr bool 3492 }{ 3493 { 3494 name: "success", 3495 clusterCfg: vtadmintestutil.TestClusterConfig{ 3496 Cluster: &vtadminpb.Cluster{ 3497 Id: "c1", 3498 Name: "cluster1", 3499 }, 3500 VtctldClient: &fakevtctldclient.VtctldClient{ 3501 GetVSchemaResults: map[string]struct { 3502 Response *vtctldatapb.GetVSchemaResponse 3503 Error error 3504 }{ 3505 "testkeyspace": { 3506 Response: &vtctldatapb.GetVSchemaResponse{ 3507 VSchema: &vschemapb.Keyspace{ 3508 Sharded: true, 3509 Vindexes: map[string]*vschemapb.Vindex{ 3510 "hash": { 3511 Type: "md5hash", 3512 }, 3513 }, 3514 }, 3515 }, 3516 }, 3517 }, 3518 }, 3519 }, 3520 req: &vtadminpb.GetVSchemaRequest{ 3521 ClusterId: "c1", 3522 Keyspace: "testkeyspace", 3523 }, 3524 expected: &vtadminpb.VSchema{ 3525 Cluster: &vtadminpb.Cluster{ 3526 Id: "c1", 3527 Name: "cluster1", 3528 }, 3529 Name: "testkeyspace", 3530 VSchema: &vschemapb.Keyspace{ 3531 Sharded: true, 3532 Vindexes: map[string]*vschemapb.Vindex{ 3533 "hash": { 3534 Type: "md5hash", 3535 }, 3536 }, 3537 }, 3538 }, 3539 shouldErr: false, 3540 }, 3541 { 3542 name: "no vschema for keyspace", 3543 clusterCfg: vtadmintestutil.TestClusterConfig{ 3544 Cluster: &vtadminpb.Cluster{ 3545 Id: "c1", 3546 Name: "cluster1", 3547 }, 3548 VtctldClient: &fakevtctldclient.VtctldClient{ 3549 GetVSchemaResults: map[string]struct { 3550 Response *vtctldatapb.GetVSchemaResponse 3551 Error error 3552 }{ 3553 "testkeyspace": { 3554 Response: &vtctldatapb.GetVSchemaResponse{ 3555 VSchema: &vschemapb.Keyspace{ 3556 Sharded: true, 3557 Vindexes: map[string]*vschemapb.Vindex{ 3558 "hash": { 3559 Type: "md5hash", 3560 }, 3561 }, 3562 }, 3563 }, 3564 }, 3565 }, 3566 }, 3567 }, 3568 req: &vtadminpb.GetVSchemaRequest{ 3569 ClusterId: "c1", 3570 Keyspace: "otherkeyspace", 3571 }, 3572 expected: nil, 3573 shouldErr: true, 3574 }, 3575 { 3576 name: "cluster not found", 3577 clusterCfg: vtadmintestutil.TestClusterConfig{ 3578 Cluster: &vtadminpb.Cluster{ 3579 Id: "c1", 3580 Name: "cluster1", 3581 }, 3582 }, 3583 req: &vtadminpb.GetVSchemaRequest{ 3584 ClusterId: "c2", 3585 Keyspace: "testkeyspace", 3586 }, 3587 expected: nil, 3588 shouldErr: true, 3589 }, 3590 } 3591 3592 ctx := context.Background() 3593 3594 for _, tt := range tests { 3595 tt := tt 3596 3597 t.Run(tt.name, func(t *testing.T) { 3598 t.Parallel() 3599 3600 clusters := []*cluster.Cluster{vtadmintestutil.BuildCluster(t, tt.clusterCfg)} 3601 api := NewAPI(clusters, Options{}) 3602 3603 resp, err := api.GetVSchema(ctx, tt.req) 3604 if tt.shouldErr { 3605 assert.Error(t, err) 3606 3607 return 3608 } 3609 3610 assert.NoError(t, err) 3611 assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp) 3612 }) 3613 } 3614 } 3615 3616 func TestGetVSchemas(t *testing.T) { 3617 t.Parallel() 3618 3619 tests := []struct { 3620 name string 3621 clusterCfgs []vtadmintestutil.TestClusterConfig 3622 req *vtadminpb.GetVSchemasRequest 3623 expected *vtadminpb.GetVSchemasResponse 3624 shouldErr bool 3625 }{ 3626 { 3627 name: "success", 3628 clusterCfgs: []vtadmintestutil.TestClusterConfig{ 3629 { 3630 Cluster: &vtadminpb.Cluster{ 3631 Id: "c1", 3632 Name: "cluster1", 3633 }, 3634 VtctldClient: &fakevtctldclient.VtctldClient{ 3635 GetKeyspacesResults: &struct { 3636 Keyspaces []*vtctldatapb.Keyspace 3637 Error error 3638 }{ 3639 Keyspaces: []*vtctldatapb.Keyspace{ 3640 { 3641 Name: "testkeyspace", 3642 }, 3643 }, 3644 }, 3645 GetVSchemaResults: map[string]struct { 3646 Response *vtctldatapb.GetVSchemaResponse 3647 Error error 3648 }{ 3649 "testkeyspace": { 3650 Response: &vtctldatapb.GetVSchemaResponse{ 3651 VSchema: &vschemapb.Keyspace{}, 3652 }, 3653 }, 3654 }, 3655 }, 3656 }, 3657 { 3658 Cluster: &vtadminpb.Cluster{ 3659 Id: "c2", 3660 Name: "cluster2", 3661 }, 3662 VtctldClient: &fakevtctldclient.VtctldClient{ 3663 GetKeyspacesResults: &struct { 3664 Keyspaces []*vtctldatapb.Keyspace 3665 Error error 3666 }{ 3667 Keyspaces: []*vtctldatapb.Keyspace{ 3668 { 3669 Name: "k2", 3670 }, 3671 }, 3672 }, 3673 GetVSchemaResults: map[string]struct { 3674 Response *vtctldatapb.GetVSchemaResponse 3675 Error error 3676 }{ 3677 "k2": { 3678 Response: &vtctldatapb.GetVSchemaResponse{ 3679 VSchema: &vschemapb.Keyspace{}, 3680 }, 3681 }, 3682 }, 3683 }, 3684 }, 3685 }, 3686 req: &vtadminpb.GetVSchemasRequest{}, 3687 expected: &vtadminpb.GetVSchemasResponse{ 3688 VSchemas: []*vtadminpb.VSchema{ 3689 { 3690 Cluster: &vtadminpb.Cluster{ 3691 Id: "c1", 3692 Name: "cluster1", 3693 }, 3694 Name: "testkeyspace", 3695 VSchema: &vschemapb.Keyspace{}, 3696 }, 3697 { 3698 Cluster: &vtadminpb.Cluster{ 3699 Id: "c2", 3700 Name: "cluster2", 3701 }, 3702 Name: "k2", 3703 VSchema: &vschemapb.Keyspace{}, 3704 }, 3705 }, 3706 }, 3707 shouldErr: false, 3708 }, 3709 { 3710 name: "requesting specific clusters", 3711 clusterCfgs: []vtadmintestutil.TestClusterConfig{ 3712 { 3713 Cluster: &vtadminpb.Cluster{ 3714 Id: "c1", 3715 Name: "cluster1", 3716 }, 3717 VtctldClient: &fakevtctldclient.VtctldClient{ 3718 GetKeyspacesResults: &struct { 3719 Keyspaces []*vtctldatapb.Keyspace 3720 Error error 3721 }{ 3722 Keyspaces: []*vtctldatapb.Keyspace{ 3723 { 3724 Name: "testkeyspace", 3725 }, 3726 }, 3727 }, 3728 GetVSchemaResults: map[string]struct { 3729 Response *vtctldatapb.GetVSchemaResponse 3730 Error error 3731 }{ 3732 "testkeyspace": { 3733 Response: &vtctldatapb.GetVSchemaResponse{ 3734 VSchema: &vschemapb.Keyspace{}, 3735 }, 3736 }, 3737 }, 3738 }, 3739 }, 3740 { 3741 Cluster: &vtadminpb.Cluster{ 3742 Id: "c2", 3743 Name: "cluster2", 3744 }, 3745 VtctldClient: &fakevtctldclient.VtctldClient{ 3746 GetKeyspacesResults: &struct { 3747 Keyspaces []*vtctldatapb.Keyspace 3748 Error error 3749 }{ 3750 Keyspaces: []*vtctldatapb.Keyspace{ 3751 { 3752 Name: "k2", 3753 }, 3754 }, 3755 }, 3756 GetVSchemaResults: map[string]struct { 3757 Response *vtctldatapb.GetVSchemaResponse 3758 Error error 3759 }{ 3760 "k2": { 3761 Response: &vtctldatapb.GetVSchemaResponse{ 3762 VSchema: &vschemapb.Keyspace{}, 3763 }, 3764 }, 3765 }, 3766 }, 3767 }, 3768 }, 3769 req: &vtadminpb.GetVSchemasRequest{ 3770 ClusterIds: []string{"c2"}, 3771 }, 3772 expected: &vtadminpb.GetVSchemasResponse{ 3773 VSchemas: []*vtadminpb.VSchema{ 3774 { 3775 Cluster: &vtadminpb.Cluster{ 3776 Id: "c2", 3777 Name: "cluster2", 3778 }, 3779 Name: "k2", 3780 VSchema: &vschemapb.Keyspace{}, 3781 }, 3782 }, 3783 }, 3784 shouldErr: false, 3785 }, 3786 { 3787 name: "GetKeyspaces failure", 3788 clusterCfgs: []vtadmintestutil.TestClusterConfig{ 3789 { 3790 Cluster: &vtadminpb.Cluster{ 3791 Id: "c1", 3792 Name: "cluster1", 3793 }, 3794 VtctldClient: &fakevtctldclient.VtctldClient{ 3795 GetKeyspacesResults: &struct { 3796 Keyspaces []*vtctldatapb.Keyspace 3797 Error error 3798 }{ 3799 Keyspaces: []*vtctldatapb.Keyspace{ 3800 { 3801 Name: "testkeyspace", 3802 }, 3803 }, 3804 }, 3805 GetVSchemaResults: map[string]struct { 3806 Response *vtctldatapb.GetVSchemaResponse 3807 Error error 3808 }{ 3809 "testkeyspace": { 3810 Response: &vtctldatapb.GetVSchemaResponse{ 3811 VSchema: &vschemapb.Keyspace{}, 3812 }, 3813 }, 3814 }, 3815 }, 3816 }, 3817 { 3818 Cluster: &vtadminpb.Cluster{ 3819 Id: "c2", 3820 Name: "cluster2", 3821 }, 3822 VtctldClient: &fakevtctldclient.VtctldClient{ 3823 GetKeyspacesResults: &struct { 3824 Keyspaces []*vtctldatapb.Keyspace 3825 Error error 3826 }{ 3827 Error: assert.AnError, 3828 }, 3829 }, 3830 }, 3831 }, 3832 req: &vtadminpb.GetVSchemasRequest{}, 3833 expected: nil, 3834 shouldErr: true, 3835 }, 3836 { 3837 name: "GetVSchema failure", 3838 clusterCfgs: []vtadmintestutil.TestClusterConfig{ 3839 { 3840 Cluster: &vtadminpb.Cluster{ 3841 Id: "c1", 3842 Name: "cluster1", 3843 }, 3844 VtctldClient: &fakevtctldclient.VtctldClient{ 3845 GetKeyspacesResults: &struct { 3846 Keyspaces []*vtctldatapb.Keyspace 3847 Error error 3848 }{ 3849 Keyspaces: []*vtctldatapb.Keyspace{ 3850 { 3851 Name: "testkeyspace", 3852 }, 3853 }, 3854 }, 3855 GetVSchemaResults: map[string]struct { 3856 Response *vtctldatapb.GetVSchemaResponse 3857 Error error 3858 }{ 3859 "testkeyspace": { 3860 Error: assert.AnError, 3861 }, 3862 }, 3863 }, 3864 }, 3865 { 3866 Cluster: &vtadminpb.Cluster{ 3867 Id: "c2", 3868 Name: "cluster2", 3869 }, 3870 VtctldClient: &fakevtctldclient.VtctldClient{ 3871 GetKeyspacesResults: &struct { 3872 Keyspaces []*vtctldatapb.Keyspace 3873 Error error 3874 }{ 3875 Keyspaces: []*vtctldatapb.Keyspace{ 3876 { 3877 Name: "k2", 3878 }, 3879 }, 3880 }, 3881 GetVSchemaResults: map[string]struct { 3882 Response *vtctldatapb.GetVSchemaResponse 3883 Error error 3884 }{ 3885 "k2": { 3886 Response: &vtctldatapb.GetVSchemaResponse{ 3887 VSchema: &vschemapb.Keyspace{}, 3888 }, 3889 }, 3890 }, 3891 }, 3892 }, 3893 }, 3894 req: &vtadminpb.GetVSchemasRequest{}, 3895 expected: nil, 3896 shouldErr: true, 3897 }, 3898 { 3899 name: "no clusters specified", 3900 clusterCfgs: []vtadmintestutil.TestClusterConfig{}, 3901 req: &vtadminpb.GetVSchemasRequest{}, 3902 expected: &vtadminpb.GetVSchemasResponse{ 3903 VSchemas: []*vtadminpb.VSchema{}, 3904 }, 3905 shouldErr: false, 3906 }, 3907 { 3908 name: "requested invalid cluster", 3909 clusterCfgs: []vtadmintestutil.TestClusterConfig{}, 3910 req: &vtadminpb.GetVSchemasRequest{ 3911 ClusterIds: []string{"c1"}, 3912 }, 3913 expected: nil, 3914 shouldErr: true, 3915 }, 3916 } 3917 3918 ctx := context.Background() 3919 3920 for _, tt := range tests { 3921 tt := tt 3922 3923 t.Run(tt.name, func(t *testing.T) { 3924 t.Parallel() 3925 3926 if tt.req == nil { 3927 t.SkipNow() 3928 } 3929 3930 clusters := vtadmintestutil.BuildClusters(t, tt.clusterCfgs...) 3931 api := NewAPI(clusters, Options{}) 3932 3933 resp, err := api.GetVSchemas(ctx, tt.req) 3934 if tt.shouldErr { 3935 assert.Error(t, err) 3936 3937 return 3938 } 3939 3940 assert.NoError(t, err) 3941 assert.ElementsMatch(t, tt.expected.VSchemas, resp.VSchemas) 3942 }) 3943 } 3944 } 3945 3946 func TestGetVtctlds(t *testing.T) { 3947 t.Parallel() 3948 3949 fakedisco1 := fakediscovery.New() 3950 cluster1 := &cluster.Cluster{ 3951 ID: "c1", 3952 Name: "cluster1", 3953 Discovery: fakedisco1, 3954 } 3955 cluster1Vtctlds := []*vtadminpb.Vtctld{ 3956 { 3957 Hostname: "cluster1-vtctld1", 3958 }, 3959 { 3960 Hostname: "cluster1-vtctld2", 3961 }, 3962 { 3963 Hostname: "cluster1-vtctld3", 3964 }, 3965 } 3966 fakedisco1.AddTaggedVtctlds(nil, cluster1Vtctlds...) 3967 3968 expectedCluster1Vtctlds := []*vtadminpb.Vtctld{ 3969 { 3970 Cluster: &vtadminpb.Cluster{ 3971 Id: cluster1.ID, 3972 Name: cluster1.Name, 3973 }, 3974 Hostname: "cluster1-vtctld1", 3975 }, 3976 { 3977 Cluster: &vtadminpb.Cluster{ 3978 Id: cluster1.ID, 3979 Name: cluster1.Name, 3980 }, 3981 Hostname: "cluster1-vtctld2", 3982 }, 3983 { 3984 Cluster: &vtadminpb.Cluster{ 3985 Id: cluster1.ID, 3986 Name: cluster1.Name, 3987 }, 3988 Hostname: "cluster1-vtctld3", 3989 }, 3990 } 3991 3992 fakedisco2 := fakediscovery.New() 3993 cluster2 := &cluster.Cluster{ 3994 ID: "c2", 3995 Name: "cluster2", 3996 Discovery: fakedisco2, 3997 } 3998 cluster2Vtctlds := []*vtadminpb.Vtctld{ 3999 { 4000 Hostname: "cluster2-vtctld1", 4001 }, 4002 } 4003 fakedisco2.AddTaggedVtctlds(nil, cluster2Vtctlds...) 4004 4005 expectedCluster2Vtctlds := []*vtadminpb.Vtctld{ 4006 { 4007 Cluster: &vtadminpb.Cluster{ 4008 Id: cluster2.ID, 4009 Name: cluster2.Name, 4010 }, 4011 Hostname: "cluster2-vtctld1", 4012 }, 4013 } 4014 4015 api := NewAPI([]*cluster.Cluster{cluster1, cluster2}, Options{}) 4016 ctx := context.Background() 4017 4018 resp, err := api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{}) 4019 assert.NoError(t, err) 4020 assert.ElementsMatch(t, append(expectedCluster1Vtctlds, expectedCluster2Vtctlds...), resp.Vtctlds) 4021 4022 resp, err = api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{ClusterIds: []string{cluster1.ID}}) 4023 assert.NoError(t, err) 4024 assert.ElementsMatch(t, expectedCluster1Vtctlds, resp.Vtctlds) 4025 4026 fakedisco1.SetVtctldsError(true) 4027 4028 resp, err = api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{}) 4029 assert.Error(t, err) 4030 assert.Nil(t, resp) 4031 } 4032 4033 func TestGetWorkflow(t *testing.T) { 4034 t.Parallel() 4035 4036 tests := []struct { 4037 name string 4038 cfgs []vtadmintestutil.TestClusterConfig 4039 req *vtadminpb.GetWorkflowRequest 4040 expected *vtadminpb.Workflow 4041 shouldErr bool 4042 }{ 4043 { 4044 name: "success", 4045 cfgs: []vtadmintestutil.TestClusterConfig{ 4046 { 4047 Cluster: &vtadminpb.Cluster{ 4048 Id: "c1", 4049 Name: "cluster1", 4050 }, 4051 VtctldClient: &fakevtctldclient.VtctldClient{ 4052 GetWorkflowsResults: map[string]struct { 4053 Response *vtctldatapb.GetWorkflowsResponse 4054 Error error 4055 }{ 4056 "testkeyspace": { 4057 Response: &vtctldatapb.GetWorkflowsResponse{ 4058 Workflows: []*vtctldatapb.Workflow{ 4059 { 4060 Name: "workflow1", 4061 }, 4062 { 4063 Name: "workflow2", 4064 }, 4065 }, 4066 }, 4067 }, 4068 }, 4069 }, 4070 }, 4071 }, 4072 req: &vtadminpb.GetWorkflowRequest{ 4073 ClusterId: "c1", 4074 Keyspace: "testkeyspace", 4075 Name: "workflow1", 4076 }, 4077 expected: &vtadminpb.Workflow{ 4078 Cluster: &vtadminpb.Cluster{ 4079 Id: "c1", 4080 Name: "cluster1", 4081 }, 4082 Keyspace: "testkeyspace", 4083 Workflow: &vtctldatapb.Workflow{ 4084 Name: "workflow1", 4085 }, 4086 }, 4087 shouldErr: false, 4088 }, 4089 { 4090 name: "no such workflow", 4091 cfgs: []vtadmintestutil.TestClusterConfig{ 4092 { 4093 Cluster: &vtadminpb.Cluster{ 4094 Id: "c1", 4095 Name: "cluster1", 4096 }, 4097 VtctldClient: &fakevtctldclient.VtctldClient{ 4098 GetWorkflowsResults: map[string]struct { 4099 Response *vtctldatapb.GetWorkflowsResponse 4100 Error error 4101 }{ 4102 "testkeyspace": { 4103 Response: &vtctldatapb.GetWorkflowsResponse{ 4104 Workflows: []*vtctldatapb.Workflow{ 4105 { 4106 Name: "workflow1", 4107 }, 4108 { 4109 Name: "workflow2", 4110 }, 4111 }, 4112 }, 4113 }, 4114 }, 4115 }, 4116 }, 4117 }, 4118 req: &vtadminpb.GetWorkflowRequest{ 4119 ClusterId: "c1", 4120 Keyspace: "testkeyspace", 4121 Name: "workflow3", 4122 }, 4123 expected: nil, 4124 shouldErr: true, 4125 }, 4126 { 4127 name: "no such cluster", 4128 cfgs: []vtadmintestutil.TestClusterConfig{}, 4129 req: &vtadminpb.GetWorkflowRequest{ 4130 ClusterId: "c1", 4131 Keyspace: "testkeyspace", 4132 Name: "workflow1", 4133 }, 4134 expected: nil, 4135 shouldErr: true, 4136 }, 4137 } 4138 4139 ctx := context.Background() 4140 4141 for _, tt := range tests { 4142 tt := tt 4143 4144 t.Run(tt.name, func(t *testing.T) { 4145 t.Parallel() 4146 4147 api := NewAPI(vtadmintestutil.BuildClusters(t, tt.cfgs...), Options{}) 4148 4149 resp, err := api.GetWorkflow(ctx, tt.req) 4150 if tt.shouldErr { 4151 assert.Error(t, err) 4152 4153 return 4154 } 4155 4156 assert.NoError(t, err) 4157 assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp) 4158 }) 4159 } 4160 } 4161 4162 func TestGetWorkflows(t *testing.T) { 4163 t.Parallel() 4164 4165 tests := []struct { 4166 name string 4167 cfgs []vtadmintestutil.TestClusterConfig 4168 req *vtadminpb.GetWorkflowsRequest 4169 expected *vtadminpb.GetWorkflowsResponse 4170 shouldErr bool 4171 }{ 4172 { 4173 name: "success", 4174 cfgs: []vtadmintestutil.TestClusterConfig{ 4175 { 4176 Cluster: &vtadminpb.Cluster{ 4177 Id: "c1", 4178 Name: "cluster1", 4179 }, 4180 VtctldClient: &fakevtctldclient.VtctldClient{ 4181 GetKeyspacesResults: &struct { 4182 Keyspaces []*vtctldatapb.Keyspace 4183 Error error 4184 }{ 4185 Keyspaces: []*vtctldatapb.Keyspace{ 4186 { 4187 Name: "testkeyspace", 4188 }, 4189 }, 4190 }, 4191 GetWorkflowsResults: map[string]struct { 4192 Response *vtctldatapb.GetWorkflowsResponse 4193 Error error 4194 }{ 4195 "testkeyspace": { 4196 Response: &vtctldatapb.GetWorkflowsResponse{ 4197 Workflows: []*vtctldatapb.Workflow{ 4198 { 4199 Name: "workflow1", 4200 }, 4201 { 4202 Name: "workflow2", 4203 }, 4204 }, 4205 }, 4206 }, 4207 }, 4208 }, 4209 }, 4210 { 4211 Cluster: &vtadminpb.Cluster{ 4212 Id: "c2", 4213 Name: "cluster2", 4214 }, 4215 VtctldClient: &fakevtctldclient.VtctldClient{ 4216 GetKeyspacesResults: &struct { 4217 Keyspaces []*vtctldatapb.Keyspace 4218 Error error 4219 }{ 4220 Keyspaces: []*vtctldatapb.Keyspace{ 4221 { 4222 Name: "otherkeyspace", 4223 }, 4224 }, 4225 }, 4226 GetWorkflowsResults: map[string]struct { 4227 Response *vtctldatapb.GetWorkflowsResponse 4228 Error error 4229 }{ 4230 "otherkeyspace": { 4231 Response: &vtctldatapb.GetWorkflowsResponse{ 4232 Workflows: []*vtctldatapb.Workflow{ 4233 { 4234 Name: "workflow1", 4235 }, 4236 }, 4237 }, 4238 }, 4239 }, 4240 }, 4241 }, 4242 }, 4243 req: &vtadminpb.GetWorkflowsRequest{}, 4244 expected: &vtadminpb.GetWorkflowsResponse{ 4245 WorkflowsByCluster: map[string]*vtadminpb.ClusterWorkflows{ 4246 "c1": { 4247 Workflows: []*vtadminpb.Workflow{ 4248 { 4249 Cluster: &vtadminpb.Cluster{ 4250 Id: "c1", 4251 Name: "cluster1", 4252 }, 4253 Keyspace: "testkeyspace", 4254 Workflow: &vtctldatapb.Workflow{ 4255 Name: "workflow1", 4256 }, 4257 }, 4258 { 4259 Cluster: &vtadminpb.Cluster{ 4260 Id: "c1", 4261 Name: "cluster1", 4262 }, 4263 Keyspace: "testkeyspace", 4264 Workflow: &vtctldatapb.Workflow{ 4265 Name: "workflow2", 4266 }, 4267 }, 4268 }, 4269 }, 4270 "c2": { 4271 Workflows: []*vtadminpb.Workflow{ 4272 { 4273 Cluster: &vtadminpb.Cluster{ 4274 Id: "c2", 4275 Name: "cluster2", 4276 }, 4277 Keyspace: "otherkeyspace", 4278 Workflow: &vtctldatapb.Workflow{ 4279 Name: "workflow1", 4280 }, 4281 }, 4282 }, 4283 }, 4284 }, 4285 }, 4286 shouldErr: false, 4287 }, 4288 { 4289 name: "one cluster has partial error then request succeeds", 4290 cfgs: []vtadmintestutil.TestClusterConfig{ 4291 { 4292 Cluster: &vtadminpb.Cluster{ 4293 Id: "c1", 4294 Name: "cluster1", 4295 }, 4296 VtctldClient: &fakevtctldclient.VtctldClient{ 4297 GetKeyspacesResults: &struct { 4298 Keyspaces []*vtctldatapb.Keyspace 4299 Error error 4300 }{ 4301 Keyspaces: []*vtctldatapb.Keyspace{ 4302 { 4303 Name: "testkeyspace", 4304 }, 4305 }, 4306 }, 4307 GetWorkflowsResults: map[string]struct { 4308 Response *vtctldatapb.GetWorkflowsResponse 4309 Error error 4310 }{ 4311 "testkeyspace": { 4312 Response: &vtctldatapb.GetWorkflowsResponse{ 4313 Workflows: []*vtctldatapb.Workflow{ 4314 { 4315 Name: "workflow1", 4316 }, 4317 { 4318 Name: "workflow2", 4319 }, 4320 }, 4321 }, 4322 }, 4323 }, 4324 }, 4325 }, 4326 { 4327 Cluster: &vtadminpb.Cluster{ 4328 Id: "c2", 4329 Name: "cluster2", 4330 }, 4331 VtctldClient: &fakevtctldclient.VtctldClient{ 4332 GetKeyspacesResults: &struct { 4333 Keyspaces []*vtctldatapb.Keyspace 4334 Error error 4335 }{ 4336 Keyspaces: []*vtctldatapb.Keyspace{ 4337 { 4338 Name: "otherkeyspace", 4339 }, 4340 { 4341 Name: "badkeyspace", 4342 }, 4343 }, 4344 }, 4345 GetWorkflowsResults: map[string]struct { 4346 Response *vtctldatapb.GetWorkflowsResponse 4347 Error error 4348 }{ 4349 "otherkeyspace": { 4350 Response: &vtctldatapb.GetWorkflowsResponse{ 4351 Workflows: []*vtctldatapb.Workflow{ 4352 { 4353 Name: "workflow1", 4354 }, 4355 }, 4356 }, 4357 }, 4358 "badkeyspace": { 4359 Error: assert.AnError, 4360 }, 4361 }, 4362 }, 4363 }, 4364 }, 4365 req: &vtadminpb.GetWorkflowsRequest{}, 4366 expected: &vtadminpb.GetWorkflowsResponse{ 4367 WorkflowsByCluster: map[string]*vtadminpb.ClusterWorkflows{ 4368 "c1": { 4369 Workflows: []*vtadminpb.Workflow{ 4370 { 4371 Cluster: &vtadminpb.Cluster{ 4372 Id: "c1", 4373 Name: "cluster1", 4374 }, 4375 Keyspace: "testkeyspace", 4376 Workflow: &vtctldatapb.Workflow{ 4377 Name: "workflow1", 4378 }, 4379 }, 4380 { 4381 Cluster: &vtadminpb.Cluster{ 4382 Id: "c1", 4383 Name: "cluster1", 4384 }, 4385 Keyspace: "testkeyspace", 4386 Workflow: &vtctldatapb.Workflow{ 4387 Name: "workflow2", 4388 }, 4389 }, 4390 }, 4391 Warnings: []string{}, 4392 }, 4393 "c2": { 4394 Workflows: []*vtadminpb.Workflow{ 4395 { 4396 Cluster: &vtadminpb.Cluster{ 4397 Id: "c2", 4398 Name: "cluster2", 4399 }, 4400 Keyspace: "otherkeyspace", 4401 Workflow: &vtctldatapb.Workflow{ 4402 Name: "workflow1", 4403 }, 4404 }, 4405 }, 4406 Warnings: []string{"some warning about badkeyspace"}, 4407 }, 4408 }, 4409 }, 4410 shouldErr: false, 4411 }, 4412 { 4413 name: "IgnoreKeyspaces applies across clusters", 4414 cfgs: []vtadmintestutil.TestClusterConfig{ 4415 { 4416 Cluster: &vtadminpb.Cluster{ 4417 Id: "c1", 4418 Name: "cluster1", 4419 }, 4420 VtctldClient: &fakevtctldclient.VtctldClient{ 4421 GetKeyspacesResults: &struct { 4422 Keyspaces []*vtctldatapb.Keyspace 4423 Error error 4424 }{ 4425 Keyspaces: []*vtctldatapb.Keyspace{ 4426 { 4427 Name: "testkeyspace", 4428 }, 4429 }, 4430 }, 4431 GetWorkflowsResults: map[string]struct { 4432 Response *vtctldatapb.GetWorkflowsResponse 4433 Error error 4434 }{ 4435 "testkeyspace": { 4436 Response: &vtctldatapb.GetWorkflowsResponse{ 4437 Workflows: []*vtctldatapb.Workflow{ 4438 { 4439 Name: "workflow1", 4440 }, 4441 { 4442 Name: "workflow2", 4443 }, 4444 }, 4445 }, 4446 }, 4447 }, 4448 }, 4449 }, 4450 { 4451 Cluster: &vtadminpb.Cluster{ 4452 Id: "c2", 4453 Name: "cluster2", 4454 }, 4455 VtctldClient: &fakevtctldclient.VtctldClient{ 4456 GetKeyspacesResults: &struct { 4457 Keyspaces []*vtctldatapb.Keyspace 4458 Error error 4459 }{ 4460 Keyspaces: []*vtctldatapb.Keyspace{ 4461 { 4462 Name: "testkeyspace", 4463 }, 4464 { 4465 Name: "otherkeyspace", 4466 }, 4467 }, 4468 }, 4469 GetWorkflowsResults: map[string]struct { 4470 Response *vtctldatapb.GetWorkflowsResponse 4471 Error error 4472 }{ 4473 "testkeyspace": { 4474 Response: &vtctldatapb.GetWorkflowsResponse{ 4475 Workflows: []*vtctldatapb.Workflow{ 4476 { 4477 Name: "workflow1", 4478 }, 4479 }, 4480 }, 4481 }, 4482 "otherkeyspace": { 4483 Response: &vtctldatapb.GetWorkflowsResponse{ 4484 Workflows: []*vtctldatapb.Workflow{ 4485 { 4486 Name: "workflow1", 4487 }, 4488 }, 4489 }, 4490 }, 4491 }, 4492 }, 4493 }, 4494 }, 4495 req: &vtadminpb.GetWorkflowsRequest{ 4496 IgnoreKeyspaces: []string{"testkeyspace"}, 4497 }, 4498 expected: &vtadminpb.GetWorkflowsResponse{ 4499 WorkflowsByCluster: map[string]*vtadminpb.ClusterWorkflows{ 4500 "c1": {}, 4501 "c2": { 4502 Workflows: []*vtadminpb.Workflow{ 4503 { 4504 Cluster: &vtadminpb.Cluster{ 4505 Id: "c2", 4506 Name: "cluster2", 4507 }, 4508 Keyspace: "otherkeyspace", 4509 Workflow: &vtctldatapb.Workflow{ 4510 Name: "workflow1", 4511 }, 4512 }, 4513 }, 4514 }, 4515 }, 4516 }, 4517 shouldErr: false, 4518 }, 4519 { 4520 name: "one cluster has fatal error, request fails", 4521 cfgs: []vtadmintestutil.TestClusterConfig{ 4522 { 4523 Cluster: &vtadminpb.Cluster{ 4524 Id: "c1", 4525 Name: "cluster1", 4526 }, 4527 VtctldClient: &fakevtctldclient.VtctldClient{ 4528 GetKeyspacesResults: &struct { 4529 Keyspaces []*vtctldatapb.Keyspace 4530 Error error 4531 }{ 4532 Keyspaces: []*vtctldatapb.Keyspace{ 4533 { 4534 Name: "testkeyspace", 4535 }, 4536 }, 4537 }, 4538 GetWorkflowsResults: map[string]struct { 4539 Response *vtctldatapb.GetWorkflowsResponse 4540 Error error 4541 }{ 4542 "testkeyspace": { 4543 Response: &vtctldatapb.GetWorkflowsResponse{ 4544 Workflows: []*vtctldatapb.Workflow{ 4545 { 4546 Name: "workflow1", 4547 }, 4548 { 4549 Name: "workflow2", 4550 }, 4551 }, 4552 }, 4553 }, 4554 }, 4555 }, 4556 }, 4557 { 4558 Cluster: &vtadminpb.Cluster{ 4559 Id: "c2", 4560 Name: "cluster2", 4561 }, 4562 VtctldClient: &fakevtctldclient.VtctldClient{ 4563 GetKeyspacesResults: &struct { 4564 Keyspaces []*vtctldatapb.Keyspace 4565 Error error 4566 }{ 4567 Error: assert.AnError, // GetKeyspaces is a fatal error 4568 }, 4569 }, 4570 }, 4571 }, 4572 req: &vtadminpb.GetWorkflowsRequest{}, 4573 expected: nil, 4574 shouldErr: true, 4575 }, 4576 } 4577 4578 ctx := context.Background() 4579 4580 for _, tt := range tests { 4581 tt := tt 4582 4583 t.Run(tt.name, func(t *testing.T) { 4584 t.Parallel() 4585 4586 api := NewAPI(vtadmintestutil.BuildClusters(t, tt.cfgs...), Options{}) 4587 4588 resp, err := api.GetWorkflows(ctx, tt.req) 4589 if tt.shouldErr { 4590 assert.Error(t, err) 4591 4592 return 4593 } 4594 4595 assert.NoError(t, err) 4596 require.NotNil(t, resp) 4597 4598 vtadmintestutil.AssertGetWorkflowsResponsesEqual(t, tt.expected, resp) 4599 }) 4600 } 4601 } 4602 4603 func TestVTExplain(t *testing.T) { 4604 t.Parallel() 4605 4606 tests := []struct { 4607 name string 4608 keyspaces []*vtctldatapb.Keyspace 4609 shards []*vtctldatapb.Shard 4610 srvVSchema *vschemapb.SrvVSchema 4611 tabletSchemas map[string]*tabletmanagerdatapb.SchemaDefinition 4612 tablets []*vtadminpb.Tablet 4613 req *vtadminpb.VTExplainRequest 4614 expectedError error 4615 }{ 4616 { 4617 name: "runs VTExplain given a valid request in a valid topology", 4618 keyspaces: []*vtctldatapb.Keyspace{ 4619 { 4620 Name: "commerce", 4621 Keyspace: &topodatapb.Keyspace{}, 4622 }, 4623 }, 4624 shards: []*vtctldatapb.Shard{ 4625 { 4626 Name: "-", 4627 Keyspace: "commerce", 4628 }, 4629 }, 4630 srvVSchema: &vschemapb.SrvVSchema{ 4631 Keyspaces: map[string]*vschemapb.Keyspace{ 4632 "commerce": { 4633 Sharded: false, 4634 Tables: map[string]*vschemapb.Table{ 4635 "customers": {}, 4636 }, 4637 }, 4638 }, 4639 RoutingRules: &vschemapb.RoutingRules{ 4640 Rules: []*vschemapb.RoutingRule{}, 4641 }, 4642 }, 4643 tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{ 4644 "c0_cell1-0000000100": { 4645 DatabaseSchema: "CREATE DATABASE commerce", 4646 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 4647 { 4648 Name: "t1", 4649 Schema: `CREATE TABLE customers (id int(11) not null,PRIMARY KEY (id));`, 4650 Type: "BASE", 4651 Columns: []string{"id"}, 4652 DataLength: 100, 4653 RowCount: 50, 4654 Fields: []*querypb.Field{ 4655 { 4656 Name: "id", 4657 Type: querypb.Type_INT32, 4658 }, 4659 }, 4660 }, 4661 }, 4662 }, 4663 }, 4664 tablets: []*vtadminpb.Tablet{ 4665 { 4666 Cluster: &vtadminpb.Cluster{ 4667 Id: "c0", 4668 Name: "cluster0", 4669 }, 4670 State: vtadminpb.Tablet_SERVING, 4671 Tablet: &topodatapb.Tablet{ 4672 Alias: &topodatapb.TabletAlias{ 4673 Uid: 100, 4674 Cell: "c0_cell1", 4675 }, 4676 Hostname: "tablet-cell1-a", 4677 Keyspace: "commerce", 4678 Shard: "-", 4679 Type: topodatapb.TabletType_REPLICA, 4680 }, 4681 }, 4682 }, 4683 req: &vtadminpb.VTExplainRequest{ 4684 Cluster: "c0", 4685 Keyspace: "commerce", 4686 Sql: "select * from customers", 4687 }, 4688 }, 4689 { 4690 name: "returns an error if no appropriate tablet found in keyspace", 4691 keyspaces: []*vtctldatapb.Keyspace{ 4692 { 4693 Name: "commerce", 4694 Keyspace: &topodatapb.Keyspace{}, 4695 }, 4696 }, 4697 shards: []*vtctldatapb.Shard{ 4698 { 4699 Name: "-", 4700 Keyspace: "commerce", 4701 }, 4702 }, 4703 srvVSchema: &vschemapb.SrvVSchema{ 4704 Keyspaces: map[string]*vschemapb.Keyspace{ 4705 "commerce": { 4706 Sharded: false, 4707 Tables: map[string]*vschemapb.Table{ 4708 "customers": {}, 4709 }, 4710 }, 4711 }, 4712 RoutingRules: &vschemapb.RoutingRules{ 4713 Rules: []*vschemapb.RoutingRule{}, 4714 }, 4715 }, 4716 tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{ 4717 "c0_cell1-0000000102": { 4718 DatabaseSchema: "CREATE DATABASE commerce", 4719 TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ 4720 { 4721 Name: "t1", 4722 Schema: `CREATE TABLE customers (id int(11) not null,PRIMARY KEY (id));`, 4723 Type: "BASE", 4724 Columns: []string{"id"}, 4725 DataLength: 100, 4726 RowCount: 50, 4727 Fields: []*querypb.Field{ 4728 { 4729 Name: "id", 4730 Type: querypb.Type_INT32, 4731 }, 4732 }, 4733 }, 4734 }, 4735 }, 4736 }, 4737 tablets: []*vtadminpb.Tablet{ 4738 { 4739 Cluster: &vtadminpb.Cluster{ 4740 Id: "c0", 4741 Name: "cluster0", 4742 }, 4743 State: vtadminpb.Tablet_SERVING, 4744 Tablet: &topodatapb.Tablet{ 4745 Alias: &topodatapb.TabletAlias{ 4746 Uid: 100, 4747 Cell: "c0_cell1", 4748 }, 4749 Hostname: "tablet-cell1-a", 4750 Keyspace: "commerce", 4751 Shard: "-", 4752 Type: topodatapb.TabletType_PRIMARY, 4753 }, 4754 }, 4755 { 4756 Cluster: &vtadminpb.Cluster{ 4757 Id: "c0", 4758 Name: "cluster0", 4759 }, 4760 State: vtadminpb.Tablet_SERVING, 4761 Tablet: &topodatapb.Tablet{ 4762 Alias: &topodatapb.TabletAlias{ 4763 Uid: 101, 4764 Cell: "c0_cell1", 4765 }, 4766 Hostname: "tablet-cell1-b", 4767 Keyspace: "commerce", 4768 Shard: "-", 4769 Type: topodatapb.TabletType_DRAINED, 4770 }, 4771 }, 4772 { 4773 Cluster: &vtadminpb.Cluster{ 4774 Id: "c0", 4775 Name: "cluster0", 4776 }, 4777 State: vtadminpb.Tablet_NOT_SERVING, 4778 Tablet: &topodatapb.Tablet{ 4779 Alias: &topodatapb.TabletAlias{ 4780 Uid: 102, 4781 Cell: "c0_cell1", 4782 }, 4783 Hostname: "tablet-cell1-c", 4784 Keyspace: "commerce", 4785 Shard: "-", 4786 Type: topodatapb.TabletType_REPLICA, 4787 }, 4788 }, 4789 }, 4790 req: &vtadminpb.VTExplainRequest{ 4791 Cluster: "c0", 4792 Keyspace: "commerce", 4793 Sql: "select * from customers", 4794 }, 4795 expectedError: vtadminerrors.ErrNoTablet, 4796 }, 4797 { 4798 name: "returns an error if cluster unspecified in request", 4799 req: &vtadminpb.VTExplainRequest{ 4800 Keyspace: "commerce", 4801 Sql: "select * from customers", 4802 }, 4803 expectedError: vtadminerrors.ErrInvalidRequest, 4804 }, 4805 { 4806 name: "returns an error if keyspace unspecified in request", 4807 req: &vtadminpb.VTExplainRequest{ 4808 Cluster: "c0", 4809 Sql: "select * from customers", 4810 }, 4811 expectedError: vtadminerrors.ErrInvalidRequest, 4812 }, 4813 { 4814 name: "returns an error if SQL unspecified in request", 4815 req: &vtadminpb.VTExplainRequest{ 4816 Cluster: "c0", 4817 Keyspace: "commerce", 4818 }, 4819 expectedError: vtadminerrors.ErrInvalidRequest, 4820 }, 4821 } 4822 4823 ctx := context.Background() 4824 4825 for _, tt := range tests { 4826 tt := tt 4827 4828 t.Run(tt.name, func(t *testing.T) { 4829 t.Parallel() 4830 4831 toposerver := memorytopo.NewServer("c0_cell1") 4832 4833 tmc := testutil.TabletManagerClient{ 4834 GetSchemaResults: map[string]struct { 4835 Schema *tabletmanagerdatapb.SchemaDefinition 4836 Error error 4837 }{}, 4838 } 4839 4840 vtctldserver := testutil.NewVtctldServerWithTabletManagerClient(t, toposerver, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer { 4841 return grpcvtctldserver.NewVtctldServer(ts) 4842 }) 4843 4844 testutil.WithTestServer(t, vtctldserver, func(t *testing.T, vtctldClient vtctldclient.VtctldClient) { 4845 if tt.srvVSchema != nil { 4846 err := toposerver.UpdateSrvVSchema(ctx, "c0_cell1", tt.srvVSchema) 4847 require.NoError(t, err) 4848 } 4849 testutil.AddKeyspaces(ctx, t, toposerver, tt.keyspaces...) 4850 testutil.AddShards(ctx, t, toposerver, tt.shards...) 4851 4852 for _, tablet := range tt.tablets { 4853 testutil.AddTablet(ctx, t, toposerver, tablet.Tablet, nil) 4854 4855 // Adds each SchemaDefinition to the fake TabletManagerClient, or nil 4856 // if there are no schemas for that tablet. (All tablet aliases must 4857 // exist in the map. Otherwise, TabletManagerClient will return an error when 4858 // looking up the schema with tablet alias that doesn't exist.) 4859 alias := topoproto.TabletAliasString(tablet.Tablet.Alias) 4860 tmc.GetSchemaResults[alias] = struct { 4861 Schema *tabletmanagerdatapb.SchemaDefinition 4862 Error error 4863 }{ 4864 Schema: tt.tabletSchemas[alias], 4865 Error: nil, 4866 } 4867 } 4868 4869 clusters := []*cluster.Cluster{ 4870 vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{ 4871 Cluster: &vtadminpb.Cluster{ 4872 Id: "c0", 4873 Name: "cluster0", 4874 }, 4875 VtctldClient: vtctldClient, 4876 Tablets: tt.tablets, 4877 }), 4878 } 4879 4880 api := NewAPI(clusters, Options{}) 4881 resp, err := api.VTExplain(ctx, tt.req) 4882 4883 if tt.expectedError != nil { 4884 assert.True(t, errors.Is(err, tt.expectedError), "expected error type %w does not match actual error type %w", err, tt.expectedError) 4885 } else { 4886 require.NoError(t, err) 4887 4888 // We don't particularly care to test the contents of the VTExplain response, 4889 // just that it exists. 4890 assert.NotEmpty(t, resp.Response) 4891 } 4892 }) 4893 }) 4894 } 4895 } 4896 4897 type ServeHTTPVtctldResponse struct { 4898 Result ServeHTTPVtctldResult `json:"result"` 4899 Ok bool `json:"ok"` 4900 } 4901 4902 type ServeHTTPVtctldResult struct { 4903 Vtctlds []*vtadminpb.Vtctld `json:"vtctlds"` 4904 } 4905 4906 type ServeHTTPResponse struct { 4907 Result ServeHTTPResult `json:"result"` 4908 Ok bool `json:"ok"` 4909 } 4910 4911 type ServeHTTPResult struct { 4912 Clusters []*vtadminpb.Cluster `json:"clusters"` 4913 } 4914 4915 func TestServeHTTP(t *testing.T) { 4916 t.Parallel() 4917 4918 testCluster, _ := cluster.Config{ 4919 ID: "dynamiccluster1", 4920 Name: "dynamiccluster1", 4921 DiscoveryImpl: "dynamic", 4922 DiscoveryFlagsByImpl: cluster.FlagsByImpl{ 4923 "dynamic": { 4924 "discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}", 4925 }, 4926 }, 4927 }.Cluster(context.Background()) 4928 4929 tests := []struct { 4930 name string 4931 cookie string 4932 enableDynamicClusters bool 4933 testClusterVtctld string 4934 clusters []*cluster.Cluster 4935 expected []*vtadminpb.Cluster 4936 expectedVtctlds []*vtadminpb.Vtctld 4937 repeat bool 4938 }{ 4939 { 4940 name: "multiple clusters without dynamic clusters", 4941 enableDynamicClusters: false, 4942 clusters: []*cluster.Cluster{ 4943 { 4944 ID: "c1", 4945 Name: "cluster1", 4946 Discovery: fakediscovery.New(), 4947 }, 4948 { 4949 ID: "c2", 4950 Name: "cluster2", 4951 Discovery: fakediscovery.New(), 4952 }, 4953 }, 4954 expected: []*vtadminpb.Cluster{ 4955 { 4956 Id: "c1", 4957 Name: "cluster1", 4958 }, 4959 { 4960 Id: "c2", 4961 Name: "cluster2", 4962 }, 4963 }, 4964 }, 4965 { 4966 name: "no clusters without dynamic clusters", 4967 enableDynamicClusters: false, 4968 clusters: []*cluster.Cluster{}, 4969 expected: []*vtadminpb.Cluster{}, 4970 }, 4971 { 4972 name: "multiple clusters with dynamic clusters", 4973 enableDynamicClusters: true, 4974 cookie: `{"id": "dynamiccluster1", "name": "dynamiccluster1", "discovery": "dynamic", "discovery-dynamic-discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}"}`, 4975 clusters: []*cluster.Cluster{ 4976 { 4977 ID: "c1", 4978 Name: "cluster1", 4979 Discovery: fakediscovery.New(), 4980 }, 4981 { 4982 ID: "c2", 4983 Name: "cluster2", 4984 Discovery: fakediscovery.New(), 4985 }, 4986 }, 4987 expected: []*vtadminpb.Cluster{ 4988 { 4989 Id: "dynamiccluster1", 4990 Name: "dynamiccluster1", 4991 }, 4992 }, 4993 }, 4994 { 4995 name: "dynamic clusters - cluster is updated when values change", 4996 enableDynamicClusters: true, 4997 cookie: `{"id": "dynamiccluster1", "name": "dynamiccluster1", "discovery": "dynamic", "discovery-dynamic-discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15001\", \"hostname\": \"localhost:15998\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}"}`, 4998 clusters: []*cluster.Cluster{ 4999 testCluster, 5000 }, 5001 expected: []*vtadminpb.Cluster{ 5002 { 5003 Id: "dynamiccluster1", 5004 Name: "dynamiccluster1", 5005 }, 5006 }, 5007 testClusterVtctld: "dynamiccluster1", 5008 expectedVtctlds: []*vtadminpb.Vtctld{ 5009 { 5010 Hostname: "localhost:15998", 5011 Cluster: &vtadminpb.Cluster{Id: "dynamiccluster1", Name: "dynamiccluster1"}, 5012 FQDN: "localhost:15001", 5013 }, 5014 }, 5015 }, 5016 { 5017 name: "multiple clusters with dynamic clusters - no duplicates", 5018 enableDynamicClusters: true, 5019 cookie: `{"id": "dynamiccluster1", "name": "dynamiccluster1", "discovery": "dynamic", "discovery-dynamic-discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}"}`, 5020 clusters: []*cluster.Cluster{ 5021 { 5022 ID: "c1", 5023 Name: "cluster1", 5024 Discovery: fakediscovery.New(), 5025 }, 5026 { 5027 ID: "c2", 5028 Name: "cluster2", 5029 Discovery: fakediscovery.New(), 5030 }, 5031 }, 5032 expected: []*vtadminpb.Cluster{ 5033 { 5034 Id: "dynamiccluster1", 5035 Name: "dynamiccluster1", 5036 }, 5037 }, 5038 repeat: true, 5039 }, 5040 { 5041 name: "multiple clusters with invalid json cookie and dynamic clusters", 5042 enableDynamicClusters: true, 5043 cookie: `{"id "dynamiccluster1", "name": "dynamiccluster1", "discovery": "dynamic", "discovery-dynamic-discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}"}`, 5044 clusters: []*cluster.Cluster{ 5045 { 5046 ID: "c1", 5047 Name: "cluster1", 5048 Discovery: fakediscovery.New(), 5049 }, 5050 { 5051 ID: "c2", 5052 Name: "cluster2", 5053 Discovery: fakediscovery.New(), 5054 }, 5055 }, 5056 expected: []*vtadminpb.Cluster{ 5057 { 5058 Id: "c1", 5059 Name: "cluster1", 5060 }, 5061 { 5062 Id: "c2", 5063 Name: "cluster2", 5064 }, 5065 }, 5066 }, 5067 { 5068 name: "no clusters with dynamic clusters", 5069 enableDynamicClusters: true, 5070 clusters: []*cluster.Cluster{}, 5071 expected: []*vtadminpb.Cluster{}, 5072 }, 5073 } 5074 5075 for _, tt := range tests { 5076 tt := tt 5077 5078 t.Run(tt.name, func(t *testing.T) { 5079 t.Parallel() 5080 5081 api := NewAPI(tt.clusters, Options{EnableDynamicClusters: tt.enableDynamicClusters}) 5082 5083 // Copy the Cookie over to a new Request 5084 req := httptest.NewRequest(http.MethodGet, "/api/clusters", nil) 5085 req.AddCookie(&http.Cookie{Name: "cluster", Value: url.QueryEscape(base64.StdEncoding.EncodeToString([]byte(tt.cookie)))}) 5086 5087 w := httptest.NewRecorder() 5088 5089 api.ServeHTTP(w, req) 5090 5091 if tt.repeat { 5092 api.ServeHTTP(w, req) 5093 } 5094 5095 res := w.Result() 5096 defer res.Body.Close() 5097 5098 dec := json.NewDecoder(res.Body) 5099 dec.DisallowUnknownFields() 5100 var clustersResponse ServeHTTPResponse 5101 err := dec.Decode(&clustersResponse) 5102 5103 assert.NoError(t, err) 5104 assert.ElementsMatch(t, tt.expected, clustersResponse.Result.Clusters) 5105 5106 if tt.testClusterVtctld != "" { 5107 req := httptest.NewRequest(http.MethodGet, "/api/vtctlds?cluster="+tt.testClusterVtctld, nil) 5108 req.AddCookie(&http.Cookie{Name: "cluster", Value: url.QueryEscape(base64.StdEncoding.EncodeToString([]byte(tt.cookie)))}) 5109 5110 w := httptest.NewRecorder() 5111 5112 api.ServeHTTP(w, req) 5113 5114 if tt.repeat { 5115 api.ServeHTTP(w, req) 5116 } 5117 5118 res := w.Result() 5119 defer res.Body.Close() 5120 5121 dec := json.NewDecoder(res.Body) 5122 dec.DisallowUnknownFields() 5123 var vtctldsResponse ServeHTTPVtctldResponse 5124 err := dec.Decode(&vtctldsResponse) 5125 5126 assert.NoError(t, err) 5127 assert.ElementsMatch(t, tt.expectedVtctlds, vtctldsResponse.Result.Vtctlds) 5128 } 5129 }) 5130 } 5131 } 5132 5133 func init() { 5134 // For tests that don't actually care about mocking the tmclient (i.e. they 5135 // call grpcvtctldserver.NewVtctldServer to initialize the unit under test), 5136 // this needs to be set. 5137 // 5138 // Tests that do care about the tmclient should use 5139 // testutil.NewVtctldServerWithTabletManagerClient to initialize their 5140 // VtctldServer. 5141 tmclienttest.SetProtocol("go.vt.vtadmin", "vtadmin.test") 5142 tmclient.RegisterTabletManagerClientFactory("vtadmin.test", func() tmclient.TabletManagerClient { 5143 return nil 5144 }) 5145 } 5146 5147 //go:generate -command authztestgen go run ./testutil/authztestgen 5148 //go:generate authztestgen -c ./testutil/authztestgen/config.json -o ./api_authz_test.go