github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/server/admin_test.go (about) 1 // Copyright 2014 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package server 12 13 import ( 14 "bytes" 15 "context" 16 "encoding/json" 17 "fmt" 18 "io/ioutil" 19 "math" 20 "net/http" 21 "net/url" 22 "reflect" 23 "regexp" 24 "sort" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/cockroachdb/cockroach/pkg/base" 30 "github.com/cockroachdb/cockroach/pkg/config/zonepb" 31 "github.com/cockroachdb/cockroach/pkg/jobs" 32 "github.com/cockroachdb/cockroach/pkg/jobs/jobspb" 33 "github.com/cockroachdb/cockroach/pkg/keys" 34 "github.com/cockroachdb/cockroach/pkg/kv/kvserver" 35 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb" 36 "github.com/cockroachdb/cockroach/pkg/roachpb" 37 "github.com/cockroachdb/cockroach/pkg/security" 38 "github.com/cockroachdb/cockroach/pkg/server/debug" 39 "github.com/cockroachdb/cockroach/pkg/server/serverpb" 40 "github.com/cockroachdb/cockroach/pkg/settings" 41 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 42 "github.com/cockroachdb/cockroach/pkg/sql" 43 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 44 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 45 "github.com/cockroachdb/cockroach/pkg/testutils" 46 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 47 "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" 48 "github.com/cockroachdb/cockroach/pkg/util/hlc" 49 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 50 "github.com/cockroachdb/cockroach/pkg/util/log" 51 "github.com/cockroachdb/cockroach/pkg/util/protoutil" 52 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 53 "github.com/cockroachdb/cockroach/pkg/util/uuid" 54 "github.com/cockroachdb/errors" 55 "github.com/gogo/protobuf/proto" 56 "github.com/stretchr/testify/assert" 57 "github.com/stretchr/testify/require" 58 ) 59 60 func getAdminJSONProto( 61 ts serverutils.TestServerInterface, path string, response protoutil.Message, 62 ) error { 63 return getAdminJSONProtoWithAdminOption(ts, path, response, true) 64 } 65 66 func getAdminJSONProtoWithAdminOption( 67 ts serverutils.TestServerInterface, path string, response protoutil.Message, isAdmin bool, 68 ) error { 69 return serverutils.GetJSONProtoWithAdminOption(ts, adminPrefix+path, response, isAdmin) 70 } 71 72 func postAdminJSONProto( 73 ts serverutils.TestServerInterface, path string, request, response protoutil.Message, 74 ) error { 75 return postAdminJSONProtoWithAdminOption(ts, path, request, response, true) 76 } 77 78 func postAdminJSONProtoWithAdminOption( 79 ts serverutils.TestServerInterface, 80 path string, 81 request, response protoutil.Message, 82 isAdmin bool, 83 ) error { 84 return serverutils.PostJSONProtoWithAdminOption(ts, adminPrefix+path, request, response, isAdmin) 85 } 86 87 // getText fetches the HTTP response body as text in the form of a 88 // byte slice from the specified URL. 89 func getText(ts serverutils.TestServerInterface, url string) ([]byte, error) { 90 httpClient, err := ts.GetAdminAuthenticatedHTTPClient() 91 if err != nil { 92 return nil, err 93 } 94 resp, err := httpClient.Get(url) 95 if err != nil { 96 return nil, err 97 } 98 defer resp.Body.Close() 99 return ioutil.ReadAll(resp.Body) 100 } 101 102 // getJSON fetches the JSON from the specified URL and returns 103 // it as unmarshaled JSON. Returns an error on any failure to fetch 104 // or unmarshal response body. 105 func getJSON(ts serverutils.TestServerInterface, url string) (interface{}, error) { 106 body, err := getText(ts, url) 107 if err != nil { 108 return nil, err 109 } 110 var jI interface{} 111 if err := json.Unmarshal(body, &jI); err != nil { 112 return nil, errors.Wrapf(err, "body is:\n%s", body) 113 } 114 return jI, nil 115 } 116 117 // debugURL returns the root debug URL. 118 func debugURL(s serverutils.TestServerInterface) string { 119 return s.AdminURL() + debug.Endpoint 120 } 121 122 // TestAdminDebugExpVar verifies that cmdline and memstats variables are 123 // available via the /debug/vars link. 124 func TestAdminDebugExpVar(t *testing.T) { 125 defer leaktest.AfterTest(t)() 126 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 127 defer s.Stopper().Stop(context.Background()) 128 129 jI, err := getJSON(s, debugURL(s)+"vars") 130 if err != nil { 131 t.Fatalf("failed to fetch JSON: %v", err) 132 } 133 j := jI.(map[string]interface{}) 134 if _, ok := j["cmdline"]; !ok { 135 t.Error("cmdline not found in JSON response") 136 } 137 if _, ok := j["memstats"]; !ok { 138 t.Error("memstats not found in JSON response") 139 } 140 } 141 142 // TestAdminDebugMetrics verifies that cmdline and memstats variables are 143 // available via the /debug/metrics link. 144 func TestAdminDebugMetrics(t *testing.T) { 145 defer leaktest.AfterTest(t)() 146 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 147 defer s.Stopper().Stop(context.Background()) 148 149 jI, err := getJSON(s, debugURL(s)+"metrics") 150 if err != nil { 151 t.Fatalf("failed to fetch JSON: %v", err) 152 } 153 j := jI.(map[string]interface{}) 154 if _, ok := j["cmdline"]; !ok { 155 t.Error("cmdline not found in JSON response") 156 } 157 if _, ok := j["memstats"]; !ok { 158 t.Error("memstats not found in JSON response") 159 } 160 } 161 162 // TestAdminDebugPprof verifies that pprof tools are available. 163 // via the /debug/pprof/* links. 164 func TestAdminDebugPprof(t *testing.T) { 165 defer leaktest.AfterTest(t)() 166 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 167 defer s.Stopper().Stop(context.Background()) 168 169 body, err := getText(s, debugURL(s)+"pprof/block?debug=1") 170 if err != nil { 171 t.Fatal(err) 172 } 173 if exp := "contention:\ncycles/second="; !bytes.Contains(body, []byte(exp)) { 174 t.Errorf("expected %s to contain %s", body, exp) 175 } 176 } 177 178 // TestAdminDebugTrace verifies that the net/trace endpoints are available 179 // via /debug/{requests,events}. 180 func TestAdminDebugTrace(t *testing.T) { 181 defer leaktest.AfterTest(t)() 182 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 183 defer s.Stopper().Stop(context.Background()) 184 185 tc := []struct { 186 segment, search string 187 }{ 188 {"requests", "<title>/debug/requests</title>"}, 189 {"events", "<title>events</title>"}, 190 } 191 192 for _, c := range tc { 193 body, err := getText(s, debugURL(s)+c.segment) 194 if err != nil { 195 t.Fatal(err) 196 } 197 if !bytes.Contains(body, []byte(c.search)) { 198 t.Errorf("expected %s to be contained in %s", c.search, body) 199 } 200 } 201 } 202 203 // TestAdminDebugRedirect verifies that the /debug/ endpoint is redirected to on 204 // incorrect /debug/ paths. 205 func TestAdminDebugRedirect(t *testing.T) { 206 defer leaktest.AfterTest(t)() 207 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 208 defer s.Stopper().Stop(context.Background()) 209 210 expURL := debugURL(s) 211 origURL := expURL + "incorrect" 212 213 // There are no particular permissions on admin endpoints, TestUser is fine. 214 client, err := testutils.NewTestBaseContext(TestUser).GetHTTPClient() 215 if err != nil { 216 t.Fatal(err) 217 } 218 219 // Don't follow redirects automatically. 220 redirectAttemptedError := errors.New("redirect") 221 client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 222 return redirectAttemptedError 223 } 224 225 resp, err := client.Get(origURL) 226 if urlError := (*url.Error)(nil); errors.As(err, &urlError) && 227 errors.Is(urlError.Err, redirectAttemptedError) { 228 // Ignore the redirectAttemptedError. 229 err = nil 230 } 231 if err != nil { 232 t.Fatal(err) 233 } else { 234 resp.Body.Close() 235 if resp.StatusCode != http.StatusMovedPermanently { 236 t.Errorf("expected status code %d; got %d", http.StatusMovedPermanently, resp.StatusCode) 237 } 238 if redirectURL, err := resp.Location(); err != nil { 239 t.Error(err) 240 } else if foundURL := redirectURL.String(); foundURL != expURL { 241 t.Errorf("expected location %s; got %s", expURL, foundURL) 242 } 243 } 244 } 245 246 func TestAdminAPIDatabases(t *testing.T) { 247 defer leaktest.AfterTest(t)() 248 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) 249 defer s.Stopper().Stop(context.Background()) 250 ts := s.(*TestServer) 251 252 ac := log.AmbientContext{Tracer: s.ClusterSettings().Tracer} 253 ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") 254 defer span.Finish() 255 256 const testdb = "test" 257 query := "CREATE DATABASE " + testdb 258 if _, err := db.Exec(query); err != nil { 259 t.Fatal(err) 260 } 261 262 // We have to create the non-admin user before calling 263 // "GRANT ... TO authenticatedUserNameNoAdmin". 264 // This is done in "GetAuthenticatedHTTPClient". 265 if _, err := ts.GetAuthenticatedHTTPClient(false); err != nil { 266 t.Fatal(err) 267 } 268 269 // Grant permissions to view the tables for the given viewing user. 270 privileges := []string{"SELECT", "UPDATE"} 271 query = fmt.Sprintf( 272 "GRANT %s ON DATABASE %s TO %s", 273 strings.Join(privileges, ", "), 274 testdb, 275 authenticatedUserNameNoAdmin, 276 ) 277 if _, err := db.Exec(query); err != nil { 278 t.Fatal(err) 279 } 280 281 for _, tc := range []struct { 282 expectedDBs []string 283 isAdmin bool 284 }{ 285 {[]string{"defaultdb", "postgres", "system", testdb}, true}, 286 {[]string{testdb}, false}, 287 } { 288 t.Run(fmt.Sprintf("isAdmin:%t", tc.isAdmin), func(t *testing.T) { 289 // Test databases endpoint. 290 var resp serverpb.DatabasesResponse 291 if err := getAdminJSONProtoWithAdminOption( 292 s, 293 "databases", 294 &resp, 295 tc.isAdmin, 296 ); err != nil { 297 t.Fatal(err) 298 } 299 300 if a, e := len(resp.Databases), len(tc.expectedDBs); a != e { 301 t.Fatalf("length of result %d != expected %d", a, e) 302 } 303 304 sort.Strings(resp.Databases) 305 for i, e := range tc.expectedDBs { 306 if a := resp.Databases[i]; a != e { 307 t.Fatalf("database name %s != expected %s", a, e) 308 } 309 } 310 311 // Test database details endpoint. 312 var details serverpb.DatabaseDetailsResponse 313 if err := getAdminJSONProtoWithAdminOption( 314 s, 315 "databases/"+testdb, 316 &details, 317 tc.isAdmin, 318 ); err != nil { 319 t.Fatal(err) 320 } 321 322 if a, e := len(details.Grants), 4; a != e { 323 t.Fatalf("# of grants %d != expected %d", a, e) 324 } 325 326 userGrants := make(map[string][]string) 327 for _, grant := range details.Grants { 328 switch grant.User { 329 case sqlbase.AdminRole, security.RootUser, authenticatedUserNameNoAdmin: 330 userGrants[grant.User] = append(userGrants[grant.User], grant.Privileges...) 331 default: 332 t.Fatalf("unknown grant to user %s", grant.User) 333 } 334 } 335 for u, p := range userGrants { 336 switch u { 337 case sqlbase.AdminRole: 338 if !reflect.DeepEqual(p, []string{"ALL"}) { 339 t.Fatalf("privileges %v != expected %v", p, privileges) 340 } 341 case security.RootUser: 342 if !reflect.DeepEqual(p, []string{"ALL"}) { 343 t.Fatalf("privileges %v != expected %v", p, privileges) 344 } 345 case authenticatedUserNameNoAdmin: 346 sort.Strings(p) 347 if !reflect.DeepEqual(p, privileges) { 348 t.Fatalf("privileges %v != expected %v", p, privileges) 349 } 350 default: 351 t.Fatalf("unknown grant to user %s", u) 352 } 353 } 354 355 // Verify Descriptor ID. 356 path, err := ts.admin.queryDescriptorIDPath(ctx, security.RootUser, []string{testdb}) 357 if err != nil { 358 t.Fatal(err) 359 } 360 if a, e := details.DescriptorID, int64(path[1]); a != e { 361 t.Fatalf("db had descriptorID %d, expected %d", a, e) 362 } 363 }) 364 } 365 } 366 367 func TestAdminAPIDatabaseDoesNotExist(t *testing.T) { 368 defer leaktest.AfterTest(t)() 369 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 370 defer s.Stopper().Stop(context.Background()) 371 372 const errPattern = "database.+does not exist" 373 if err := getAdminJSONProto(s, "databases/i_do_not_exist", nil); !testutils.IsError(err, errPattern) { 374 t.Fatalf("unexpected error: %v\nexpected: %s", err, errPattern) 375 } 376 } 377 378 func TestAdminAPIDatabaseSQLInjection(t *testing.T) { 379 defer leaktest.AfterTest(t)() 380 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 381 defer s.Stopper().Stop(context.Background()) 382 383 const fakedb = "system;DROP DATABASE system;" 384 const path = "databases/" + fakedb 385 const errPattern = `target database or schema does not exist` 386 if err := getAdminJSONProto(s, path, nil); !testutils.IsError(err, errPattern) { 387 t.Fatalf("unexpected error: %v\nexpected: %s", err, errPattern) 388 } 389 } 390 391 func TestAdminAPINonTableStats(t *testing.T) { 392 defer leaktest.AfterTest(t)() 393 testCluster := serverutils.StartTestCluster(t, 3, base.TestClusterArgs{}) 394 defer testCluster.Stopper().Stop(context.Background()) 395 s := testCluster.Server(0) 396 397 // Skip TableStatsResponse.Stats comparison, since it includes data which 398 // aren't consistent (time, bytes). 399 expectedResponse := serverpb.NonTableStatsResponse{ 400 TimeSeriesStats: &serverpb.TableStatsResponse{ 401 RangeCount: 1, 402 ReplicaCount: 3, 403 NodeCount: 3, 404 }, 405 InternalUseStats: &serverpb.TableStatsResponse{ 406 RangeCount: 9, 407 ReplicaCount: 12, 408 NodeCount: 3, 409 }, 410 } 411 412 var resp serverpb.NonTableStatsResponse 413 if err := getAdminJSONProto(s, "nontablestats", &resp); err != nil { 414 t.Fatal(err) 415 } 416 417 assertExpectedStatsResponse := func(expected, actual *serverpb.TableStatsResponse) { 418 assert.Equal(t, expected.RangeCount, actual.RangeCount) 419 assert.Equal(t, expected.ReplicaCount, actual.ReplicaCount) 420 assert.Equal(t, expected.NodeCount, actual.NodeCount) 421 } 422 423 assertExpectedStatsResponse(expectedResponse.TimeSeriesStats, resp.TimeSeriesStats) 424 assertExpectedStatsResponse(expectedResponse.InternalUseStats, resp.InternalUseStats) 425 } 426 427 // Verify that for a cluster with no user data, all the ranges on the Databases 428 // page consist of: 429 // 1) the total ranges listed for the system database 430 // 2) the total ranges listed for the Non-Table data 431 func TestRangeCount(t *testing.T) { 432 defer leaktest.AfterTest(t)() 433 testCluster := serverutils.StartTestCluster(t, 3, base.TestClusterArgs{}) 434 defer testCluster.Stopper().Stop(context.Background()) 435 s := testCluster.Server(0) 436 437 // Sum up ranges for non-table parts of the system returned 438 // from the "nontablestats" enpoint. 439 getNonTableRangeCount := func() (ts, internal int64) { 440 var resp serverpb.NonTableStatsResponse 441 if err := getAdminJSONProto(s, "nontablestats", &resp); err != nil { 442 t.Fatal(err) 443 } 444 return resp.TimeSeriesStats.RangeCount, resp.InternalUseStats.RangeCount 445 } 446 447 // Return map tablename=>count obtained from the 448 // "databases/system/tables/{table}" endpoints. 449 getSystemTableRangeCount := func() map[string]int64 { 450 m := map[string]int64{} 451 var dbResp serverpb.DatabaseDetailsResponse 452 if err := getAdminJSONProto(s, "databases/system", &dbResp); err != nil { 453 t.Fatal(err) 454 } 455 for _, tableName := range dbResp.TableNames { 456 var tblResp serverpb.TableStatsResponse 457 path := "databases/system/tables/" + tableName + "/stats" 458 if err := getAdminJSONProto(s, path, &tblResp); err != nil { 459 t.Fatal(err) 460 } 461 m[tableName] = tblResp.RangeCount 462 } 463 return m 464 } 465 466 getRangeCountFromFullSpan := func() int64 { 467 adminServer := s.(*TestServer).Server.admin 468 stats, err := adminServer.statsForSpan(context.Background(), roachpb.Span{ 469 Key: keys.LocalMax, 470 EndKey: keys.MaxKey, 471 }) 472 if err != nil { 473 t.Fatal(err) 474 } 475 return stats.RangeCount 476 } 477 478 exp := getRangeCountFromFullSpan() 479 480 sysDBMap := getSystemTableRangeCount() 481 { 482 // The tables below sit on the SystemConfigRange. For technical reason, 483 // their range count comes back as zero. Let's just use the descriptor 484 // table to count this range as they're not picked up by the "non-table 485 // data" neither. 486 for _, table := range []string{"descriptor", "settings", "namespace", "zones"} { 487 n, ok := sysDBMap[table] 488 require.True(t, ok, table) 489 require.Zero(t, n, table) 490 } 491 492 sysDBMap["descriptor"] = 1 493 494 } 495 var systemTableRangeCount int64 496 for _, n := range sysDBMap { 497 systemTableRangeCount += n 498 } 499 500 tsCount, internalCount := getNonTableRangeCount() 501 502 act := tsCount + internalCount + systemTableRangeCount 503 504 if !assert.Equal(t, 505 exp, 506 act, 507 ) { 508 t.Log("did nonTableDescriptorRangeCount() change?") 509 t.Logf( 510 "claimed numbers:\ntime series = %d\ninternal = %d\nsystemdb = %d (%v)", 511 tsCount, internalCount, systemTableRangeCount, sysDBMap, 512 ) 513 db := testCluster.ServerConn(0) 514 defer db.Close() 515 516 runner := sqlutils.MakeSQLRunner(db) 517 s := sqlutils.MatrixToStr(runner.QueryStr(t, ` 518 select range_id, database_name, table_name, start_pretty, end_pretty from crdb_internal.ranges order by range_id asc`, 519 )) 520 t.Logf("actual ranges:\n%s", s) 521 } 522 } 523 524 func TestAdminAPITableDoesNotExist(t *testing.T) { 525 defer leaktest.AfterTest(t)() 526 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 527 defer s.Stopper().Stop(context.Background()) 528 529 const fakename = "i_do_not_exist" 530 const badDBPath = "databases/" + fakename + "/tables/foo" 531 const dbErrPattern = `relation \\"` + fakename + `.foo\\" does not exist` 532 if err := getAdminJSONProto(s, badDBPath, nil); !testutils.IsError(err, dbErrPattern) { 533 t.Fatalf("unexpected error: %v\nexpected: %s", err, dbErrPattern) 534 } 535 536 const badTablePath = "databases/system/tables/" + fakename 537 const tableErrPattern = `relation \\"system.` + fakename + `\\" does not exist` 538 if err := getAdminJSONProto(s, badTablePath, nil); !testutils.IsError(err, tableErrPattern) { 539 t.Fatalf("unexpected error: %v\nexpected: %s", err, tableErrPattern) 540 } 541 } 542 543 func TestAdminAPITableSQLInjection(t *testing.T) { 544 defer leaktest.AfterTest(t)() 545 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 546 defer s.Stopper().Stop(context.Background()) 547 548 const fakeTable = "users;DROP DATABASE system;" 549 const path = "databases/system/tables/" + fakeTable 550 const errPattern = `relation \"system.` + fakeTable + `\" does not exist` 551 if err := getAdminJSONProto(s, path, nil); !testutils.IsError(err, regexp.QuoteMeta(errPattern)) { 552 t.Fatalf("unexpected error: %v\nexpected: %s", err, errPattern) 553 } 554 } 555 556 func TestAdminAPITableDetails(t *testing.T) { 557 defer leaktest.AfterTest(t)() 558 559 for _, tc := range []struct { 560 name, dbName, tblName string 561 }{ 562 {name: "lower", dbName: "test", tblName: "tbl"}, 563 {name: "lower with space", dbName: "test test", tblName: "tbl tbl"}, 564 {name: "upper", dbName: "TEST", tblName: "TBL"}, // Regression test for issue #14056 565 } { 566 t.Run(tc.name, func(t *testing.T) { 567 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) 568 defer s.Stopper().Stop(context.Background()) 569 ts := s.(*TestServer) 570 571 escDBName := tree.NameStringP(&tc.dbName) 572 escTblName := tree.NameStringP(&tc.tblName) 573 574 ac := log.AmbientContext{Tracer: s.ClusterSettings().Tracer} 575 ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") 576 defer span.Finish() 577 578 setupQueries := []string{ 579 fmt.Sprintf("CREATE DATABASE %s", escDBName), 580 fmt.Sprintf(`CREATE TABLE %s.%s ( 581 nulls_allowed INT8, 582 nulls_not_allowed INT8 NOT NULL DEFAULT 1000, 583 default2 INT8 DEFAULT 2, 584 string_default STRING DEFAULT 'default_string', 585 INDEX descidx (default2 DESC) 586 )`, escDBName, escTblName), 587 fmt.Sprintf("CREATE USER readonly"), 588 fmt.Sprintf("CREATE USER app"), 589 fmt.Sprintf("GRANT SELECT ON %s.%s TO readonly", escDBName, escTblName), 590 fmt.Sprintf("GRANT SELECT,UPDATE,DELETE ON %s.%s TO app", escDBName, escTblName), 591 } 592 593 for _, q := range setupQueries { 594 if _, err := db.Exec(q); err != nil { 595 t.Fatal(err) 596 } 597 } 598 599 // Perform API call. 600 var resp serverpb.TableDetailsResponse 601 url := fmt.Sprintf("databases/%s/tables/%s", tc.dbName, tc.tblName) 602 if err := getAdminJSONProto(s, url, &resp); err != nil { 603 t.Fatal(err) 604 } 605 606 // Verify columns. 607 expColumns := []serverpb.TableDetailsResponse_Column{ 608 {Name: "nulls_allowed", Type: "INT8", Nullable: true, DefaultValue: ""}, 609 {Name: "nulls_not_allowed", Type: "INT8", Nullable: false, DefaultValue: "1000:::INT8"}, 610 {Name: "default2", Type: "INT8", Nullable: true, DefaultValue: "2:::INT8"}, 611 {Name: "string_default", Type: "STRING", Nullable: true, DefaultValue: "'default_string':::STRING"}, 612 {Name: "rowid", Type: "INT8", Nullable: false, DefaultValue: "unique_rowid()", Hidden: true}, 613 } 614 testutils.SortStructs(expColumns, "Name") 615 testutils.SortStructs(resp.Columns, "Name") 616 if a, e := len(resp.Columns), len(expColumns); a != e { 617 t.Fatalf("# of result columns %d != expected %d (got: %#v)", a, e, resp.Columns) 618 } 619 for i, a := range resp.Columns { 620 e := expColumns[i] 621 if a.String() != e.String() { 622 t.Fatalf("mismatch at column %d: actual %#v != %#v", i, a, e) 623 } 624 } 625 626 // Verify grants. 627 expGrants := []serverpb.TableDetailsResponse_Grant{ 628 {User: sqlbase.AdminRole, Privileges: []string{"ALL"}}, 629 {User: security.RootUser, Privileges: []string{"ALL"}}, 630 {User: "app", Privileges: []string{"DELETE"}}, 631 {User: "app", Privileges: []string{"SELECT"}}, 632 {User: "app", Privileges: []string{"UPDATE"}}, 633 {User: "readonly", Privileges: []string{"SELECT"}}, 634 } 635 testutils.SortStructs(expGrants, "User") 636 testutils.SortStructs(resp.Grants, "User") 637 if a, e := len(resp.Grants), len(expGrants); a != e { 638 t.Fatalf("# of grant columns %d != expected %d (got: %#v)", a, e, resp.Grants) 639 } 640 for i, a := range resp.Grants { 641 e := expGrants[i] 642 sort.Strings(a.Privileges) 643 sort.Strings(e.Privileges) 644 if a.String() != e.String() { 645 t.Fatalf("mismatch at index %d: actual %#v != %#v", i, a, e) 646 } 647 } 648 649 // Verify indexes. 650 expIndexes := []serverpb.TableDetailsResponse_Index{ 651 {Name: "primary", Column: "rowid", Direction: "ASC", Unique: true, Seq: 1}, 652 {Name: "descidx", Column: "rowid", Direction: "ASC", Unique: false, Seq: 2, Implicit: true}, 653 {Name: "descidx", Column: "default2", Direction: "DESC", Unique: false, Seq: 1}, 654 } 655 testutils.SortStructs(expIndexes, "Name", "Seq") 656 testutils.SortStructs(resp.Indexes, "Name", "Seq") 657 for i, a := range resp.Indexes { 658 e := expIndexes[i] 659 if a.String() != e.String() { 660 t.Fatalf("mismatch at index %d: actual %#v != %#v", i, a, e) 661 } 662 } 663 664 // Verify range count. 665 if a, e := resp.RangeCount, int64(1); a != e { 666 t.Fatalf("# of ranges %d != expected %d", a, e) 667 } 668 669 // Verify Create Table Statement. 670 { 671 672 showCreateTableQuery := fmt.Sprintf("SHOW CREATE TABLE %s.%s", escDBName, escTblName) 673 674 row := db.QueryRow(showCreateTableQuery) 675 var createStmt, tableName string 676 if err := row.Scan(&tableName, &createStmt); err != nil { 677 t.Fatal(err) 678 } 679 680 if a, e := resp.CreateTableStatement, createStmt; a != e { 681 t.Fatalf("mismatched create table statement; expected %s, got %s", e, a) 682 } 683 } 684 685 // Verify Descriptor ID. 686 path, err := ts.admin.queryDescriptorIDPath(ctx, 687 security.RootUser, []string{tc.dbName, tc.tblName}) 688 if err != nil { 689 t.Fatal(err) 690 } 691 if a, e := resp.DescriptorID, int64(path[2]); a != e { 692 t.Fatalf("table had descriptorID %d, expected %d", a, e) 693 } 694 }) 695 } 696 } 697 698 // TestAdminAPIZoneDetails verifies the zone configuration information returned 699 // for both DatabaseDetailsResponse AND TableDetailsResponse. 700 func TestAdminAPIZoneDetails(t *testing.T) { 701 defer leaktest.AfterTest(t)() 702 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) 703 defer s.Stopper().Stop(context.Background()) 704 ts := s.(*TestServer) 705 706 // Create database and table. 707 ac := log.AmbientContext{Tracer: s.ClusterSettings().Tracer} 708 ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") 709 defer span.Finish() 710 setupQueries := []string{ 711 "CREATE DATABASE test", 712 "CREATE TABLE test.tbl (val STRING)", 713 } 714 for _, q := range setupQueries { 715 if _, err := db.Exec(q); err != nil { 716 t.Fatalf("error executing '%s': %s", q, err) 717 } 718 } 719 720 // Function to verify the zone for table "test.tbl" as returned by the Admin 721 // API. 722 verifyTblZone := func( 723 expectedZone zonepb.ZoneConfig, expectedLevel serverpb.ZoneConfigurationLevel, 724 ) { 725 var resp serverpb.TableDetailsResponse 726 if err := getAdminJSONProto(s, "databases/test/tables/tbl", &resp); err != nil { 727 t.Fatal(err) 728 } 729 if a, e := &resp.ZoneConfig, &expectedZone; !proto.Equal(a, e) { 730 t.Errorf("actual table zone config %v did not match expected value %v", a, e) 731 } 732 if a, e := resp.ZoneConfigLevel, expectedLevel; a != e { 733 t.Errorf("actual table ZoneConfigurationLevel %s did not match expected value %s", a, e) 734 } 735 if t.Failed() { 736 t.FailNow() 737 } 738 } 739 740 // Function to verify the zone for database "test" as returned by the Admin 741 // API. 742 verifyDbZone := func( 743 expectedZone zonepb.ZoneConfig, expectedLevel serverpb.ZoneConfigurationLevel, 744 ) { 745 var resp serverpb.DatabaseDetailsResponse 746 if err := getAdminJSONProto(s, "databases/test", &resp); err != nil { 747 t.Fatal(err) 748 } 749 if a, e := &resp.ZoneConfig, &expectedZone; !proto.Equal(a, e) { 750 t.Errorf("actual db zone config %v did not match expected value %v", a, e) 751 } 752 if a, e := resp.ZoneConfigLevel, expectedLevel; a != e { 753 t.Errorf("actual db ZoneConfigurationLevel %s did not match expected value %s", a, e) 754 } 755 if t.Failed() { 756 t.FailNow() 757 } 758 } 759 760 // Function to store a zone config for a given object ID. 761 setZone := func(zoneCfg zonepb.ZoneConfig, id sqlbase.ID) { 762 zoneBytes, err := protoutil.Marshal(&zoneCfg) 763 if err != nil { 764 t.Fatal(err) 765 } 766 const query = `INSERT INTO system.zones VALUES($1, $2)` 767 if _, err := db.Exec(query, id, zoneBytes); err != nil { 768 t.Fatalf("error executing '%s': %s", query, err) 769 } 770 } 771 772 // Verify zone matches cluster default. 773 verifyDbZone(s.(*TestServer).Cfg.DefaultZoneConfig, serverpb.ZoneConfigurationLevel_CLUSTER) 774 verifyTblZone(s.(*TestServer).Cfg.DefaultZoneConfig, serverpb.ZoneConfigurationLevel_CLUSTER) 775 776 // Get ID path for table. This will be an array of three IDs, containing the ID of the root namespace, 777 // the database, and the table (in that order). 778 idPath, err := ts.admin.queryDescriptorIDPath(ctx, security.RootUser, []string{"test", "tbl"}) 779 if err != nil { 780 t.Fatal(err) 781 } 782 783 // Apply zone configuration to database and check again. 784 dbZone := zonepb.ZoneConfig{ 785 RangeMinBytes: proto.Int64(456), 786 } 787 setZone(dbZone, idPath[1]) 788 verifyDbZone(dbZone, serverpb.ZoneConfigurationLevel_DATABASE) 789 verifyTblZone(dbZone, serverpb.ZoneConfigurationLevel_DATABASE) 790 791 // Apply zone configuration to table and check again. 792 tblZone := zonepb.ZoneConfig{ 793 RangeMinBytes: proto.Int64(789), 794 } 795 setZone(tblZone, idPath[2]) 796 verifyDbZone(dbZone, serverpb.ZoneConfigurationLevel_DATABASE) 797 verifyTblZone(tblZone, serverpb.ZoneConfigurationLevel_TABLE) 798 } 799 800 func TestAdminAPIUsers(t *testing.T) { 801 defer leaktest.AfterTest(t)() 802 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) 803 defer s.Stopper().Stop(context.Background()) 804 805 // Create sample users. 806 query := ` 807 INSERT INTO system.users (username, "hashedPassword") 808 VALUES ('adminUser', 'abc'), ('bob', 'xyz')` 809 if _, err := db.Exec(query); err != nil { 810 t.Fatal(err) 811 } 812 813 // Query the API for users. 814 var resp serverpb.UsersResponse 815 if err := getAdminJSONProto(s, "users", &resp); err != nil { 816 t.Fatal(err) 817 } 818 expResult := serverpb.UsersResponse{ 819 Users: []serverpb.UsersResponse_User{ 820 {Username: "adminUser"}, 821 {Username: "authentic_user"}, 822 {Username: "bob"}, 823 {Username: "root"}, 824 }, 825 } 826 827 // Verify results. 828 const sortKey = "Username" 829 testutils.SortStructs(resp.Users, sortKey) 830 testutils.SortStructs(expResult.Users, sortKey) 831 if !reflect.DeepEqual(resp, expResult) { 832 t.Fatalf("result %v != expected %v", resp, expResult) 833 } 834 } 835 836 func TestAdminAPIEvents(t *testing.T) { 837 defer leaktest.AfterTest(t)() 838 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) 839 defer s.Stopper().Stop(context.Background()) 840 841 setupQueries := []string{ 842 "CREATE DATABASE api_test", 843 "CREATE TABLE api_test.tbl1 (a INT)", 844 "CREATE TABLE api_test.tbl2 (a INT)", 845 "CREATE TABLE api_test.tbl3 (a INT)", 846 "DROP TABLE api_test.tbl1", 847 "DROP TABLE api_test.tbl2", 848 "SET CLUSTER SETTING cluster.organization = 'somestring';", 849 } 850 for _, q := range setupQueries { 851 if _, err := db.Exec(q); err != nil { 852 t.Fatalf("error executing '%s': %s", q, err) 853 } 854 } 855 856 const allEvents = "" 857 type testcase struct { 858 eventType sql.EventLogType 859 hasLimit bool 860 limit int 861 unredacted bool 862 expCount int 863 } 864 testcases := []testcase{ 865 {sql.EventLogNodeJoin, false, 0, false, 1}, 866 {sql.EventLogNodeRestart, false, 0, false, 0}, 867 {sql.EventLogDropDatabase, false, 0, false, 0}, 868 {sql.EventLogCreateDatabase, false, 0, false, 3}, 869 {sql.EventLogDropTable, false, 0, false, 2}, 870 {sql.EventLogCreateTable, false, 0, false, 3}, 871 {sql.EventLogSetClusterSetting, false, 0, false, 4}, 872 // We use limit=true with no limit here because otherwise the 873 // expCount will mess up the expected total count below. 874 {sql.EventLogSetClusterSetting, true, 0, true, 4}, 875 {sql.EventLogCreateTable, true, 0, false, 3}, 876 {sql.EventLogCreateTable, true, -1, false, 3}, 877 {sql.EventLogCreateTable, true, 2, false, 2}, 878 } 879 minTotalEvents := 0 880 for _, tc := range testcases { 881 if !tc.hasLimit { 882 minTotalEvents += tc.expCount 883 } 884 } 885 testcases = append(testcases, testcase{allEvents, false, 0, false, minTotalEvents}) 886 887 for i, tc := range testcases { 888 url := "events" 889 if tc.eventType != allEvents { 890 url += "?type=" + string(tc.eventType) 891 if tc.hasLimit { 892 url += fmt.Sprintf("&limit=%d", tc.limit) 893 } 894 if tc.unredacted { 895 url += fmt.Sprintf("&unredacted_events=true") 896 } 897 } 898 899 t.Run(url, func(t *testing.T) { 900 var resp serverpb.EventsResponse 901 if err := getAdminJSONProto(s, url, &resp); err != nil { 902 t.Fatal(err) 903 } 904 if tc.eventType == allEvents { 905 // When retrieving all events, we expect that there will be some system 906 // database migrations, unrelated to this test, that add to the log entry 907 // count. So, we do a looser check here. 908 if a, min := len(resp.Events), tc.expCount; a < tc.expCount { 909 t.Fatalf("%d: total # of events %d < min %d", i, a, min) 910 } 911 } else { 912 if a, e := len(resp.Events), tc.expCount; a != e { 913 t.Fatalf("%d: # of %s events %d != expected %d", i, tc.eventType, a, e) 914 } 915 } 916 917 // Ensure we don't have blank / nonsensical fields. 918 for _, e := range resp.Events { 919 if e.Timestamp == (time.Time{}) { 920 t.Errorf("%d: missing/empty timestamp", i) 921 } 922 923 if len(tc.eventType) > 0 { 924 if a, e := e.EventType, string(tc.eventType); a != e { 925 t.Errorf("%d: event type %s != expected %s", i, a, e) 926 } 927 } else { 928 if len(e.EventType) == 0 { 929 t.Errorf("%d: missing event type in event", i) 930 } 931 } 932 933 isSettingChange := e.EventType == string(sql.EventLogSetClusterSetting) 934 935 if e.TargetID == 0 && !isSettingChange { 936 t.Errorf("%d: missing/empty TargetID", i) 937 } 938 if e.ReportingID == 0 { 939 t.Errorf("%d: missing/empty ReportingID", i) 940 } 941 if len(e.Info) == 0 { 942 t.Errorf("%d: missing/empty Info", i) 943 } 944 if isSettingChange && strings.Contains(e.Info, "cluster.organization") { 945 if tc.unredacted { 946 if !strings.Contains(e.Info, "somestring") { 947 t.Errorf("%d: require 'somestring' in Info", i) 948 } 949 } else { 950 if strings.Contains(e.Info, "somestring") { 951 t.Errorf("%d: un-redacted 'somestring' in Info", i) 952 } 953 } 954 } 955 if len(e.UniqueID) == 0 { 956 t.Errorf("%d: missing/empty UniqueID", i) 957 } 958 } 959 }) 960 } 961 } 962 963 func TestAdminAPISettings(t *testing.T) { 964 defer leaktest.AfterTest(t)() 965 966 sc := log.Scope(t) 967 defer sc.Close(t) 968 969 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 970 defer s.Stopper().Stop(context.Background()) 971 972 // Any bool that defaults to true will work here. 973 const settingKey = "sql.metrics.statement_details.enabled" 974 st := s.ClusterSettings() 975 allKeys := settings.Keys() 976 977 checkSetting := func(t *testing.T, k string, v serverpb.SettingsResponse_Value) { 978 ref, ok := settings.Lookup(k, settings.LookupForReporting) 979 if !ok { 980 t.Fatalf("%s: not found after initial lookup", k) 981 } 982 typ := ref.Typ() 983 984 if !settings.TestingIsReportable(ref) { 985 if v.Value != "<redacted>" && v.Value != "" { 986 t.Errorf("%s: expected redacted value for %v, got %s", k, ref, v.Value) 987 } 988 } else { 989 if ref.String(&st.SV) != v.Value { 990 t.Errorf("%s: expected value %v, got %s", k, ref, v.Value) 991 } 992 } 993 994 if expectedPublic := ref.Visibility() == settings.Public; expectedPublic != v.Public { 995 t.Errorf("%s: expected public %v, got %v", k, expectedPublic, v.Public) 996 } 997 998 if desc := ref.Description(); desc != v.Description { 999 t.Errorf("%s: expected description %s, got %s", k, desc, v.Description) 1000 } 1001 if typ != v.Type { 1002 t.Errorf("%s: expected type %s, got %s", k, typ, v.Type) 1003 } 1004 } 1005 1006 t.Run("all", func(t *testing.T) { 1007 var resp serverpb.SettingsResponse 1008 1009 if err := getAdminJSONProto(s, "settings", &resp); err != nil { 1010 t.Fatal(err) 1011 } 1012 1013 // Check that all expected keys were returned 1014 if len(allKeys) != len(resp.KeyValues) { 1015 t.Fatalf("expected %d keys, got %d", len(allKeys), len(resp.KeyValues)) 1016 } 1017 for _, k := range allKeys { 1018 if _, ok := resp.KeyValues[k]; !ok { 1019 t.Fatalf("expected key %s not found in response", k) 1020 } 1021 } 1022 1023 // Check that the test key is listed and the values come indeed 1024 // from the settings package unchanged. 1025 seenRef := false 1026 for k, v := range resp.KeyValues { 1027 if k == settingKey { 1028 seenRef = true 1029 if v.Value != "true" { 1030 t.Errorf("%s: expected true, got %s", k, v.Value) 1031 } 1032 } 1033 1034 checkSetting(t, k, v) 1035 } 1036 1037 if !seenRef { 1038 t.Fatalf("failed to observe test setting %s, got %+v", settingKey, resp.KeyValues) 1039 } 1040 }) 1041 1042 t.Run("one-by-one", func(t *testing.T) { 1043 var resp serverpb.SettingsResponse 1044 1045 // All the settings keys must be retrievable, and their 1046 // type and description must match. 1047 for _, k := range allKeys { 1048 q := make(url.Values) 1049 q.Add("keys", k) 1050 url := "settings?" + q.Encode() 1051 if err := getAdminJSONProto(s, url, &resp); err != nil { 1052 t.Fatalf("%s: %v", k, err) 1053 } 1054 if len(resp.KeyValues) != 1 { 1055 t.Fatalf("%s: expected 1 response, got %d", k, len(resp.KeyValues)) 1056 } 1057 v, ok := resp.KeyValues[k] 1058 if !ok { 1059 t.Fatalf("%s: response does not contain key", k) 1060 } 1061 1062 checkSetting(t, k, v) 1063 } 1064 }) 1065 } 1066 1067 // TestAdminAPIUIData checks that UI customizations are properly 1068 // persisted for both admin and non-admin users. 1069 func TestAdminAPIUIData(t *testing.T) { 1070 defer leaktest.AfterTest(t)() 1071 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 1072 defer s.Stopper().Stop(context.Background()) 1073 1074 testutils.RunTrueAndFalse(t, "isAdmin", func(t *testing.T, isAdmin bool) { 1075 start := timeutil.Now() 1076 1077 mustSetUIData := func(keyValues map[string][]byte) { 1078 if err := postAdminJSONProtoWithAdminOption(s, "uidata", &serverpb.SetUIDataRequest{ 1079 KeyValues: keyValues, 1080 }, &serverpb.SetUIDataResponse{}, isAdmin); err != nil { 1081 t.Fatal(err) 1082 } 1083 } 1084 1085 expectKeyValues := func(expKeyValues map[string][]byte) { 1086 var resp serverpb.GetUIDataResponse 1087 queryValues := make(url.Values) 1088 for key := range expKeyValues { 1089 queryValues.Add("keys", key) 1090 } 1091 url := "uidata?" + queryValues.Encode() 1092 if err := getAdminJSONProtoWithAdminOption(s, url, &resp, isAdmin); err != nil { 1093 t.Fatal(err) 1094 } 1095 // Do a two-way comparison. We can't use reflect.DeepEqual(), because 1096 // resp.KeyValues has timestamps and expKeyValues doesn't. 1097 for key, actualVal := range resp.KeyValues { 1098 if a, e := actualVal.Value, expKeyValues[key]; !bytes.Equal(a, e) { 1099 t.Fatalf("key %s: value = %v, expected = %v", key, a, e) 1100 } 1101 } 1102 for key, expVal := range expKeyValues { 1103 if a, e := resp.KeyValues[key].Value, expVal; !bytes.Equal(a, e) { 1104 t.Fatalf("key %s: value = %v, expected = %v", key, a, e) 1105 } 1106 } 1107 1108 // Sanity check LastUpdated. 1109 for _, val := range resp.KeyValues { 1110 now := timeutil.Now() 1111 if val.LastUpdated.Before(start) { 1112 t.Fatalf("val.LastUpdated %s < start %s", val.LastUpdated, start) 1113 } 1114 if val.LastUpdated.After(now) { 1115 t.Fatalf("val.LastUpdated %s > now %s", val.LastUpdated, now) 1116 } 1117 } 1118 } 1119 1120 expectValueEquals := func(key string, expVal []byte) { 1121 expectKeyValues(map[string][]byte{key: expVal}) 1122 } 1123 1124 expectKeyNotFound := func(key string) { 1125 var resp serverpb.GetUIDataResponse 1126 url := "uidata?keys=" + key 1127 if err := getAdminJSONProtoWithAdminOption(s, url, &resp, isAdmin); err != nil { 1128 t.Fatal(err) 1129 } 1130 if len(resp.KeyValues) != 0 { 1131 t.Fatal("key unexpectedly found") 1132 } 1133 } 1134 1135 // Basic tests. 1136 var badResp serverpb.GetUIDataResponse 1137 const errPattern = "400 Bad Request" 1138 if err := getAdminJSONProtoWithAdminOption(s, "uidata", &badResp, isAdmin); !testutils.IsError(err, errPattern) { 1139 t.Fatalf("unexpected error: %v\nexpected: %s", err, errPattern) 1140 } 1141 1142 mustSetUIData(map[string][]byte{"k1": []byte("v1")}) 1143 expectValueEquals("k1", []byte("v1")) 1144 1145 expectKeyNotFound("NON_EXISTENT_KEY") 1146 1147 mustSetUIData(map[string][]byte{ 1148 "k2": []byte("v2"), 1149 "k3": []byte("v3"), 1150 }) 1151 expectValueEquals("k2", []byte("v2")) 1152 expectValueEquals("k3", []byte("v3")) 1153 expectKeyValues(map[string][]byte{ 1154 "k2": []byte("v2"), 1155 "k3": []byte("v3"), 1156 }) 1157 1158 mustSetUIData(map[string][]byte{"k2": []byte("v2-updated")}) 1159 expectKeyValues(map[string][]byte{ 1160 "k2": []byte("v2-updated"), 1161 "k3": []byte("v3"), 1162 }) 1163 1164 // Write a binary blob with all possible byte values, then verify it. 1165 var buf bytes.Buffer 1166 for i := 0; i < 997; i++ { 1167 buf.WriteByte(byte(i % 256)) 1168 } 1169 mustSetUIData(map[string][]byte{"bin": buf.Bytes()}) 1170 expectValueEquals("bin", buf.Bytes()) 1171 }) 1172 } 1173 1174 // TestAdminAPIUISeparateData check that separate users have separate customizations. 1175 func TestAdminAPIUISeparateData(t *testing.T) { 1176 defer leaktest.AfterTest(t)() 1177 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 1178 defer s.Stopper().Stop(context.Background()) 1179 1180 // Make a setting for an admin user. 1181 if err := postAdminJSONProtoWithAdminOption(s, "uidata", 1182 &serverpb.SetUIDataRequest{KeyValues: map[string][]byte{"k": []byte("v1")}}, 1183 &serverpb.SetUIDataResponse{}, 1184 true /*isAdmin*/); err != nil { 1185 t.Fatal(err) 1186 } 1187 1188 // Make a setting for a non-admin user. 1189 if err := postAdminJSONProtoWithAdminOption(s, "uidata", 1190 &serverpb.SetUIDataRequest{KeyValues: map[string][]byte{"k": []byte("v2")}}, 1191 &serverpb.SetUIDataResponse{}, 1192 false /*isAdmin*/); err != nil { 1193 t.Fatal(err) 1194 } 1195 1196 var resp serverpb.GetUIDataResponse 1197 url := "uidata?keys=k" 1198 1199 if err := getAdminJSONProtoWithAdminOption(s, url, &resp, true /* isAdmin */); err != nil { 1200 t.Fatal(err) 1201 } 1202 if len(resp.KeyValues) != 1 || !bytes.Equal(resp.KeyValues["k"].Value, []byte("v1")) { 1203 t.Fatalf("unexpected admin values: %+v", resp.KeyValues) 1204 } 1205 if err := getAdminJSONProtoWithAdminOption(s, url, &resp, false /* isAdmin */); err != nil { 1206 t.Fatal(err) 1207 } 1208 if len(resp.KeyValues) != 1 || !bytes.Equal(resp.KeyValues["k"].Value, []byte("v2")) { 1209 t.Fatalf("unexpected non-admin values: %+v", resp.KeyValues) 1210 } 1211 } 1212 1213 func TestClusterAPI(t *testing.T) { 1214 defer leaktest.AfterTest(t)() 1215 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) 1216 defer s.Stopper().Stop(context.Background()) 1217 1218 testutils.RunTrueAndFalse(t, "reportingOn", func(t *testing.T, reportingOn bool) { 1219 testutils.RunTrueAndFalse(t, "enterpriseOn", func(t *testing.T, enterpriseOn bool) { 1220 // Override server license check. 1221 if enterpriseOn { 1222 old := base.CheckEnterpriseEnabled 1223 base.CheckEnterpriseEnabled = func(_ *cluster.Settings, _ uuid.UUID, _, _ string) error { 1224 return nil 1225 } 1226 defer func() { base.CheckEnterpriseEnabled = old }() 1227 } 1228 1229 if _, err := db.Exec(`SET CLUSTER SETTING diagnostics.reporting.enabled = $1`, reportingOn); err != nil { 1230 t.Fatal(err) 1231 } 1232 1233 // We need to retry, because the cluster ID isn't set until after 1234 // bootstrapping and because setting a cluster setting isn't necessarily 1235 // instantaneous. 1236 // 1237 // Also note that there's a migration that affects `diagnostics.reporting.enabled`, 1238 // so manipulating the cluster setting var directly is a bad idea. 1239 testutils.SucceedsSoon(t, func() error { 1240 var resp serverpb.ClusterResponse 1241 if err := getAdminJSONProto(s, "cluster", &resp); err != nil { 1242 return err 1243 } 1244 if a, e := resp.ClusterID, s.RPCContext().ClusterID.String(); a != e { 1245 return errors.Errorf("cluster ID %s != expected %s", a, e) 1246 } 1247 if a, e := resp.ReportingEnabled, reportingOn; a != e { 1248 return errors.Errorf("reportingEnabled = %t, wanted %t", a, e) 1249 } 1250 if a, e := resp.EnterpriseEnabled, enterpriseOn; a != e { 1251 return errors.Errorf("enterpriseEnabled = %t, wanted %t", a, e) 1252 } 1253 return nil 1254 }) 1255 }) 1256 }) 1257 } 1258 1259 func TestHealthAPI(t *testing.T) { 1260 defer leaktest.AfterTest(t)() 1261 1262 ctx := context.Background() 1263 1264 s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) 1265 defer s.Stopper().Stop(ctx) 1266 1267 // We need to retry because the node ID isn't set until after 1268 // bootstrapping. 1269 testutils.SucceedsSoon(t, func() error { 1270 var resp serverpb.HealthResponse 1271 return getAdminJSONProto(s, "health", &resp) 1272 }) 1273 1274 // Expire this node's liveness record by pausing heartbeats and advancing the 1275 // server's clock. 1276 ts := s.(*TestServer) 1277 defer ts.nodeLiveness.DisableAllHeartbeatsForTest()() 1278 self, err := ts.nodeLiveness.Self() 1279 if err != nil { 1280 t.Fatal(err) 1281 } 1282 s.Clock().Update(hlc.Timestamp(self.Expiration).Add(1, 0)) 1283 1284 var resp serverpb.HealthResponse 1285 testutils.SucceedsSoon(t, func() error { 1286 err := getAdminJSONProto(s, "health?ready=1", &resp) 1287 if err == nil { 1288 return errors.New("health OK, still waiting for unhealth") 1289 } 1290 1291 t.Logf("observed error: %v", err) 1292 if !testutils.IsError(err, `(?s)503 Service Unavailable.*"error": "node is not healthy"`) { 1293 return err 1294 } 1295 return nil 1296 }) 1297 1298 // After the node reports an error with `?ready=1`, the health 1299 // endpoint must still succeed without error when `?ready=1` is not specified. 1300 if err := getAdminJSONProto(s, "health", &resp); err != nil { 1301 t.Fatal(err) 1302 } 1303 } 1304 1305 // getSystemJobIDs queries the jobs table for all jobs IDs. Sorted by decreasing creation time. 1306 func getSystemJobIDs(t testing.TB, db *sqlutils.SQLRunner) []int64 { 1307 rows := db.Query(t, `SELECT job_id FROM crdb_internal.jobs ORDER BY created DESC;`) 1308 defer rows.Close() 1309 1310 res := []int64{} 1311 for rows.Next() { 1312 var id int64 1313 if err := rows.Scan(&id); err != nil { 1314 t.Fatal(err) 1315 } 1316 res = append(res, id) 1317 } 1318 return res 1319 } 1320 1321 func TestAdminAPIJobs(t *testing.T) { 1322 defer leaktest.AfterTest(t)() 1323 1324 s, conn, _ := serverutils.StartServer(t, base.TestServerArgs{}) 1325 defer s.Stopper().Stop(context.Background()) 1326 sqlDB := sqlutils.MakeSQLRunner(conn) 1327 1328 // Get list of existing jobs (migrations). Assumed to all have succeeded. 1329 existingIDs := getSystemJobIDs(t, sqlDB) 1330 1331 testJobs := []struct { 1332 id int64 1333 status jobs.Status 1334 details jobspb.Details 1335 progress jobspb.ProgressDetails 1336 username string 1337 }{ 1338 {1, jobs.StatusRunning, jobspb.RestoreDetails{}, jobspb.RestoreProgress{}, security.RootUser}, 1339 {2, jobs.StatusRunning, jobspb.BackupDetails{}, jobspb.BackupProgress{}, security.RootUser}, 1340 {3, jobs.StatusSucceeded, jobspb.BackupDetails{}, jobspb.BackupProgress{}, security.RootUser}, 1341 {4, jobs.StatusRunning, jobspb.ChangefeedDetails{}, jobspb.ChangefeedProgress{}, security.RootUser}, 1342 {5, jobs.StatusSucceeded, jobspb.BackupDetails{}, jobspb.BackupProgress{}, authenticatedUserNameNoAdmin}, 1343 } 1344 for _, job := range testJobs { 1345 payload := jobspb.Payload{Username: job.username, Details: jobspb.WrapPayloadDetails(job.details)} 1346 payloadBytes, err := protoutil.Marshal(&payload) 1347 if err != nil { 1348 t.Fatal(err) 1349 } 1350 1351 progress := jobspb.Progress{Details: jobspb.WrapProgressDetails(job.progress)} 1352 // Populate progress.Progress field with a specific progress type based on 1353 // the job type. 1354 if _, ok := job.progress.(jobspb.ChangefeedProgress); ok { 1355 progress.Progress = &jobspb.Progress_HighWater{ 1356 HighWater: &hlc.Timestamp{}, 1357 } 1358 } else { 1359 progress.Progress = &jobspb.Progress_FractionCompleted{ 1360 FractionCompleted: 1.0, 1361 } 1362 } 1363 1364 progressBytes, err := protoutil.Marshal(&progress) 1365 if err != nil { 1366 t.Fatal(err) 1367 } 1368 sqlDB.Exec(t, 1369 `INSERT INTO system.jobs (id, status, payload, progress) VALUES ($1, $2, $3, $4)`, 1370 job.id, job.status, payloadBytes, progressBytes, 1371 ) 1372 } 1373 1374 const invalidJobType = math.MaxInt32 1375 1376 testCases := []struct { 1377 uri string 1378 expectedIDsViaAdmin []int64 1379 expectedIDsViaNonAdmin []int64 1380 }{ 1381 {"jobs", append([]int64{5, 4, 3, 2, 1}, existingIDs...), []int64{5}}, 1382 {"jobs?limit=1", []int64{5}, []int64{5}}, 1383 {"jobs?status=running", []int64{4, 2, 1}, []int64{}}, 1384 {"jobs?status=succeeded", append([]int64{5, 3}, existingIDs...), []int64{5}}, 1385 {"jobs?status=pending", []int64{}, []int64{}}, 1386 {"jobs?status=garbage", []int64{}, []int64{}}, 1387 {fmt.Sprintf("jobs?type=%d", jobspb.TypeBackup), []int64{5, 3, 2}, []int64{5}}, 1388 {fmt.Sprintf("jobs?type=%d", jobspb.TypeRestore), []int64{1}, []int64{}}, 1389 {fmt.Sprintf("jobs?type=%d", invalidJobType), []int64{}, []int64{}}, 1390 {fmt.Sprintf("jobs?status=running&type=%d", jobspb.TypeBackup), []int64{2}, []int64{}}, 1391 } 1392 1393 testutils.RunTrueAndFalse(t, "isAdmin", func(t *testing.T, isAdmin bool) { 1394 for i, testCase := range testCases { 1395 var res serverpb.JobsResponse 1396 if err := getAdminJSONProtoWithAdminOption(s, testCase.uri, &res, isAdmin); err != nil { 1397 t.Fatal(err) 1398 } 1399 resIDs := []int64{} 1400 for _, job := range res.Jobs { 1401 resIDs = append(resIDs, job.ID) 1402 } 1403 1404 expected := testCase.expectedIDsViaAdmin 1405 if !isAdmin { 1406 expected = testCase.expectedIDsViaNonAdmin 1407 } 1408 1409 if e, a := expected, resIDs; !reflect.DeepEqual(e, a) { 1410 t.Errorf("%d: expected job IDs %v, but got %v", i, e, a) 1411 } 1412 } 1413 }) 1414 } 1415 1416 func TestAdminAPILocations(t *testing.T) { 1417 defer leaktest.AfterTest(t)() 1418 1419 s, conn, _ := serverutils.StartServer(t, base.TestServerArgs{}) 1420 defer s.Stopper().Stop(context.Background()) 1421 sqlDB := sqlutils.MakeSQLRunner(conn) 1422 1423 testLocations := []struct { 1424 localityKey string 1425 localityValue string 1426 latitude float64 1427 longitude float64 1428 }{ 1429 {"city", "Des Moines", 41.60054, -93.60911}, 1430 {"city", "New York City", 40.71427, -74.00597}, 1431 {"city", "Seattle", 47.60621, -122.33207}, 1432 } 1433 for _, loc := range testLocations { 1434 sqlDB.Exec(t, 1435 `INSERT INTO system.locations ("localityKey", "localityValue", latitude, longitude) VALUES ($1, $2, $3, $4)`, 1436 loc.localityKey, loc.localityValue, loc.latitude, loc.longitude, 1437 ) 1438 } 1439 var res serverpb.LocationsResponse 1440 if err := getAdminJSONProto(s, "locations", &res); err != nil { 1441 t.Fatal(err) 1442 } 1443 for i, loc := range testLocations { 1444 expLoc := serverpb.LocationsResponse_Location{ 1445 LocalityKey: loc.localityKey, 1446 LocalityValue: loc.localityValue, 1447 Latitude: loc.latitude, 1448 Longitude: loc.longitude, 1449 } 1450 if !reflect.DeepEqual(res.Locations[i], expLoc) { 1451 t.Errorf("%d: expected location %v, but got %v", i, expLoc, res.Locations[i]) 1452 } 1453 } 1454 } 1455 1456 func TestAdminAPIQueryPlan(t *testing.T) { 1457 defer leaktest.AfterTest(t)() 1458 1459 s, conn, _ := serverutils.StartServer(t, base.TestServerArgs{}) 1460 defer s.Stopper().Stop(context.Background()) 1461 sqlDB := sqlutils.MakeSQLRunner(conn) 1462 1463 sqlDB.Exec(t, `CREATE DATABASE api_test`) 1464 sqlDB.Exec(t, `CREATE TABLE api_test.t1 (id int primary key, name string)`) 1465 sqlDB.Exec(t, `CREATE TABLE api_test.t2 (id int primary key, name string)`) 1466 1467 testCases := []struct { 1468 query string 1469 exp []string 1470 }{ 1471 {"SELECT sum(id) FROM api_test.t1", []string{"nodeNames\":[\"1\"]", "Out: @1"}}, 1472 {"SELECT sum(1) FROM api_test.t1 JOIN api_test.t2 on t1.id = t2.id", []string{"nodeNames\":[\"1\"]", "Out: @1"}}, 1473 } 1474 for i, testCase := range testCases { 1475 var res serverpb.QueryPlanResponse 1476 queryParam := url.QueryEscape(testCase.query) 1477 if err := getAdminJSONProto(s, fmt.Sprintf("queryplan?query=%s", queryParam), &res); err != nil { 1478 t.Errorf("%d: got error %s", i, err) 1479 } 1480 1481 for _, exp := range testCase.exp { 1482 if !strings.Contains(res.DistSQLPhysicalQueryPlan, exp) { 1483 t.Errorf("%d: expected response %v to contain %s", i, res, exp) 1484 } 1485 } 1486 } 1487 1488 } 1489 1490 func TestAdminAPIRangeLogByRangeID(t *testing.T) { 1491 defer leaktest.AfterTest(t)() 1492 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) 1493 defer s.Stopper().Stop(context.Background()) 1494 1495 rangeID := 654321 1496 testCases := []struct { 1497 rangeID int 1498 hasLimit bool 1499 limit int 1500 expected int 1501 }{ 1502 {rangeID, true, 0, 2}, 1503 {rangeID, true, -1, 2}, 1504 {rangeID, true, 1, 1}, 1505 {rangeID, false, 0, 2}, 1506 // We'll create one event that has rangeID+1 as the otherRangeID. 1507 {rangeID + 1, false, 0, 1}, 1508 } 1509 1510 for _, otherRangeID := range []int{rangeID + 1, rangeID + 2} { 1511 if _, err := db.Exec( 1512 `INSERT INTO system.rangelog ( 1513 timestamp, "rangeID", "otherRangeID", "storeID", "eventType" 1514 ) VALUES ( 1515 now(), $1, $2, $3, $4 1516 )`, 1517 rangeID, otherRangeID, 1518 1, // storeID 1519 kvserverpb.RangeLogEventType_add.String(), 1520 ); err != nil { 1521 t.Fatal(err) 1522 } 1523 } 1524 1525 for _, tc := range testCases { 1526 url := fmt.Sprintf("rangelog/%d", tc.rangeID) 1527 if tc.hasLimit { 1528 url += fmt.Sprintf("?limit=%d", tc.limit) 1529 } 1530 t.Run(url, func(t *testing.T) { 1531 var resp serverpb.RangeLogResponse 1532 if err := getAdminJSONProto(s, url, &resp); err != nil { 1533 t.Fatal(err) 1534 } 1535 1536 if e, a := tc.expected, len(resp.Events); e != a { 1537 t.Fatalf("expected %d events, got %d", e, a) 1538 } 1539 1540 for _, event := range resp.Events { 1541 expID := roachpb.RangeID(tc.rangeID) 1542 if event.Event.RangeID != expID && event.Event.OtherRangeID != expID { 1543 t.Errorf("expected rangeID or otherRangeID to be %d, got %d and r%d", 1544 expID, event.Event.RangeID, event.Event.OtherRangeID) 1545 } 1546 } 1547 }) 1548 } 1549 } 1550 1551 // Test the range log API when queries are not filtered by a range ID (like in 1552 // TestAdminAPIRangeLogByRangeID). 1553 func TestAdminAPIFullRangeLog(t *testing.T) { 1554 defer leaktest.AfterTest(t)() 1555 s, db, _ := serverutils.StartServer(t, 1556 base.TestServerArgs{ 1557 Knobs: base.TestingKnobs{ 1558 Store: &kvserver.StoreTestingKnobs{ 1559 DisableSplitQueue: true, 1560 }, 1561 }, 1562 }) 1563 defer s.Stopper().Stop(context.Background()) 1564 1565 // Insert something in the rangelog table, otherwise it's empty for new 1566 // clusters. 1567 rows, err := db.Query(`SELECT count(1) FROM system.rangelog`) 1568 if err != nil { 1569 t.Fatal(err) 1570 } 1571 if !rows.Next() { 1572 t.Fatal("missing row") 1573 } 1574 var cnt int 1575 if err := rows.Scan(&cnt); err != nil { 1576 t.Fatal(err) 1577 } 1578 if err := rows.Close(); err != nil { 1579 t.Fatal(err) 1580 } 1581 if cnt != 0 { 1582 t.Fatalf("expected 0 rows in system.rangelog, found: %d", cnt) 1583 } 1584 const rangeID = 100 1585 for i := 0; i < 10; i++ { 1586 if _, err := db.Exec( 1587 `INSERT INTO system.rangelog ( 1588 timestamp, "rangeID", "storeID", "eventType" 1589 ) VALUES (now(), $1, 1, $2)`, 1590 rangeID, 1591 kvserverpb.RangeLogEventType_add.String(), 1592 ); err != nil { 1593 t.Fatal(err) 1594 } 1595 } 1596 expectedEvents := 10 1597 1598 testCases := []struct { 1599 hasLimit bool 1600 limit int 1601 expected int 1602 }{ 1603 {false, 0, expectedEvents}, 1604 {true, 0, expectedEvents}, 1605 {true, -1, expectedEvents}, 1606 {true, 1, 1}, 1607 } 1608 1609 for _, tc := range testCases { 1610 url := "rangelog" 1611 if tc.hasLimit { 1612 url += fmt.Sprintf("?limit=%d", tc.limit) 1613 } 1614 t.Run(url, func(t *testing.T) { 1615 var resp serverpb.RangeLogResponse 1616 if err := getAdminJSONProto(s, url, &resp); err != nil { 1617 t.Fatal(err) 1618 } 1619 events := resp.Events 1620 if e, a := tc.expected, len(events); e != a { 1621 var sb strings.Builder 1622 for _, ev := range events { 1623 sb.WriteString(ev.String() + "\n") 1624 } 1625 t.Fatalf("expected %d events, got %d:\n%s", e, a, sb.String()) 1626 } 1627 }) 1628 } 1629 } 1630 1631 func TestAdminAPIDataDistribution(t *testing.T) { 1632 defer leaktest.AfterTest(t)() 1633 1634 testCluster := serverutils.StartTestCluster(t, 3, base.TestClusterArgs{}) 1635 defer testCluster.Stopper().Stop(context.Background()) 1636 1637 firstServer := testCluster.Server(0) 1638 sqlDB := sqlutils.MakeSQLRunner(testCluster.ServerConn(0)) 1639 1640 // Create some tables. 1641 sqlDB.Exec(t, `CREATE DATABASE roachblog`) 1642 sqlDB.Exec(t, `CREATE TABLE roachblog.posts (id INT PRIMARY KEY, title text, body text)`) 1643 sqlDB.Exec(t, `CREATE TABLE roachblog.comments ( 1644 id INT PRIMARY KEY, 1645 post_id INT REFERENCES roachblog.posts, 1646 body text 1647 )`) 1648 // Test special characters in DB and table names. 1649 sqlDB.Exec(t, `CREATE DATABASE "sp'ec\ch""ars"`) 1650 sqlDB.Exec(t, `CREATE TABLE "sp'ec\ch""ars"."more\spec'chars" (id INT PRIMARY KEY)`) 1651 1652 // Verify that we see their replicas in the DataDistribution response, evenly spread 1653 // across the test cluster's three nodes. 1654 1655 expectedDatabaseInfo := map[string]serverpb.DataDistributionResponse_DatabaseInfo{ 1656 "roachblog": { 1657 TableInfo: map[string]serverpb.DataDistributionResponse_TableInfo{ 1658 "posts": { 1659 ReplicaCountByNodeId: map[roachpb.NodeID]int64{ 1660 1: 1, 1661 2: 1, 1662 3: 1, 1663 }, 1664 }, 1665 "comments": { 1666 ReplicaCountByNodeId: map[roachpb.NodeID]int64{ 1667 1: 1, 1668 2: 1, 1669 3: 1, 1670 }, 1671 }, 1672 }, 1673 }, 1674 `sp'ec\ch"ars`: { 1675 TableInfo: map[string]serverpb.DataDistributionResponse_TableInfo{ 1676 `more\spec'chars`: { 1677 ReplicaCountByNodeId: map[roachpb.NodeID]int64{ 1678 1: 1, 1679 2: 1, 1680 3: 1, 1681 }, 1682 }, 1683 }, 1684 }, 1685 } 1686 1687 // Wait for the new tables' ranges to be created and replicated. 1688 testutils.SucceedsSoon(t, func() error { 1689 var resp serverpb.DataDistributionResponse 1690 if err := getAdminJSONProto(firstServer, "data_distribution", &resp); err != nil { 1691 t.Fatal(err) 1692 } 1693 1694 delete(resp.DatabaseInfo, "system") // delete results for system database. 1695 if !reflect.DeepEqual(resp.DatabaseInfo, expectedDatabaseInfo) { 1696 return fmt.Errorf("expected %v; got %v", expectedDatabaseInfo, resp.DatabaseInfo) 1697 } 1698 1699 // Don't test anything about the zone configs for now; just verify that something is there. 1700 if len(resp.ZoneConfigs) == 0 { 1701 return fmt.Errorf("no zone configs returned") 1702 } 1703 1704 return nil 1705 }) 1706 1707 // Verify that the request still works after a table has been dropped, 1708 // and that dropped_at is set on the dropped table. 1709 sqlDB.Exec(t, `DROP TABLE roachblog.comments`) 1710 1711 var resp serverpb.DataDistributionResponse 1712 if err := getAdminJSONProto(firstServer, "data_distribution", &resp); err != nil { 1713 t.Fatal(err) 1714 } 1715 1716 if resp.DatabaseInfo["roachblog"].TableInfo["comments"].DroppedAt == nil { 1717 t.Fatal("expected roachblog.comments to have dropped_at set but it's nil") 1718 } 1719 1720 // Verify that the request still works after a database has been dropped. 1721 sqlDB.Exec(t, `DROP DATABASE roachblog CASCADE`) 1722 1723 if err := getAdminJSONProto(firstServer, "data_distribution", &resp); err != nil { 1724 t.Fatal(err) 1725 } 1726 } 1727 1728 func BenchmarkAdminAPIDataDistribution(b *testing.B) { 1729 if testing.Short() { 1730 b.Skip("TODO: fix benchmark") 1731 } 1732 testCluster := serverutils.StartTestCluster(b, 3, base.TestClusterArgs{}) 1733 defer testCluster.Stopper().Stop(context.Background()) 1734 1735 firstServer := testCluster.Server(0) 1736 sqlDB := sqlutils.MakeSQLRunner(testCluster.ServerConn(0)) 1737 1738 sqlDB.Exec(b, `CREATE DATABASE roachblog`) 1739 1740 // Create a bunch of tables. 1741 for i := 0; i < 200; i++ { 1742 sqlDB.Exec( 1743 b, 1744 fmt.Sprintf(`CREATE TABLE roachblog.t%d (id INT PRIMARY KEY, title text, body text)`, i), 1745 ) 1746 // TODO(vilterp): split to increase the number of ranges for each table 1747 } 1748 1749 b.ResetTimer() 1750 for n := 0; n < b.N; n++ { 1751 var resp serverpb.DataDistributionResponse 1752 if err := getAdminJSONProto(firstServer, "data_distribution", &resp); err != nil { 1753 b.Fatal(err) 1754 } 1755 } 1756 b.StopTimer() 1757 } 1758 1759 func TestEnqueueRange(t *testing.T) { 1760 defer leaktest.AfterTest(t)() 1761 testCluster := serverutils.StartTestCluster(t, 3, base.TestClusterArgs{ 1762 ReplicationMode: base.ReplicationManual, 1763 }) 1764 defer testCluster.Stopper().Stop(context.Background()) 1765 1766 // Up-replicate r1 to all 3 nodes. We use manual replication to avoid lease 1767 // transfers causing temporary conditions in which no store is the 1768 // leaseholder, which can break the the tests below. 1769 _, err := testCluster.AddReplicas(roachpb.KeyMin, testCluster.Target(1), testCluster.Target(2)) 1770 if err != nil { 1771 t.Fatal(err) 1772 } 1773 1774 // RangeID being queued 1775 const realRangeID = 1 1776 const fakeRangeID = 999 1777 1778 // Who we expect responses from. 1779 const none = 0 1780 const leaseholder = 1 1781 const allReplicas = 3 1782 1783 testCases := []struct { 1784 nodeID roachpb.NodeID 1785 queue string 1786 rangeID roachpb.RangeID 1787 expectedDetails int 1788 expectedNonErrors int 1789 }{ 1790 // Success cases 1791 {0, "gc", realRangeID, allReplicas, leaseholder}, 1792 {0, "split", realRangeID, allReplicas, leaseholder}, 1793 {0, "replicaGC", realRangeID, allReplicas, allReplicas}, 1794 {0, "RaFtLoG", realRangeID, allReplicas, allReplicas}, 1795 {0, "RAFTSNAPSHOT", realRangeID, allReplicas, allReplicas}, 1796 {0, "consistencyChecker", realRangeID, allReplicas, leaseholder}, 1797 {0, "TIMESERIESmaintenance", realRangeID, allReplicas, leaseholder}, 1798 {1, "raftlog", realRangeID, leaseholder, leaseholder}, 1799 {2, "raftlog", realRangeID, leaseholder, 1}, 1800 {3, "raftlog", realRangeID, leaseholder, 1}, 1801 // Error cases 1802 {0, "gv", realRangeID, allReplicas, none}, 1803 {0, "GC", fakeRangeID, allReplicas, none}, 1804 } 1805 1806 for _, tc := range testCases { 1807 t.Run(tc.queue, func(t *testing.T) { 1808 req := &serverpb.EnqueueRangeRequest{ 1809 NodeID: tc.nodeID, 1810 Queue: tc.queue, 1811 RangeID: tc.rangeID, 1812 } 1813 var resp serverpb.EnqueueRangeResponse 1814 if err := postAdminJSONProto(testCluster.Server(0), "enqueue_range", req, &resp); err != nil { 1815 t.Fatal(err) 1816 } 1817 if e, a := tc.expectedDetails, len(resp.Details); e != a { 1818 t.Errorf("expected %d details; got %d: %+v", e, a, resp) 1819 } 1820 var numNonErrors int 1821 for _, details := range resp.Details { 1822 if len(details.Events) > 0 && details.Error == "" { 1823 numNonErrors++ 1824 } 1825 } 1826 if tc.expectedNonErrors != numNonErrors { 1827 t.Errorf("expected %d non-error details; got %d: %+v", tc.expectedNonErrors, numNonErrors, resp) 1828 } 1829 }) 1830 } 1831 1832 // Finally, test a few more basic error cases. 1833 reqs := []*serverpb.EnqueueRangeRequest{ 1834 {NodeID: -1, Queue: "gc"}, 1835 {Queue: ""}, 1836 {RangeID: -1, Queue: "gc"}, 1837 } 1838 for _, req := range reqs { 1839 t.Run(fmt.Sprint(req), func(t *testing.T) { 1840 var resp serverpb.EnqueueRangeResponse 1841 err := postAdminJSONProto(testCluster.Server(0), "enqueue_range", req, &resp) 1842 if err == nil { 1843 t.Fatalf("unexpected success: %+v", resp) 1844 } 1845 if !testutils.IsError(err, "400 Bad Request") { 1846 t.Fatalf("unexpected error type: %+v", err) 1847 } 1848 }) 1849 } 1850 } 1851 1852 func TestStatsforSpanOnLocalMax(t *testing.T) { 1853 defer leaktest.AfterTest(t)() 1854 testCluster := serverutils.StartTestCluster(t, 3, base.TestClusterArgs{}) 1855 defer testCluster.Stopper().Stop(context.Background()) 1856 firstServer := testCluster.Server(0) 1857 adminServer := firstServer.(*TestServer).Server.admin 1858 1859 underTest := roachpb.Span{ 1860 Key: keys.LocalMax, 1861 EndKey: keys.SystemPrefix, 1862 } 1863 1864 _, err := adminServer.statsForSpan(context.Background(), underTest) 1865 if err != nil { 1866 t.Fatal(err) 1867 } 1868 }