func TestAdminAPIUsers(t *testing.T) { defer leaktest.AfterTest(t)() s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) defer s.Stopper().Stop() ts := s.(*TestServer) // Create sample users. ac := log.AmbientContext{Tracer: tracing.NewTracer()} ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") defer span.Finish() session := sql.NewSession( ctx, sql.SessionArgs{User: security.RootUser}, ts.sqlExecutor, nil, &sql.MemoryMetrics{}) session.StartUnlimitedMonitor() defer session.Finish(ts.sqlExecutor) query := ` INSERT INTO system.users (username, hashedPassword) VALUES ('admin', 'abc'), ('bob', 'xyz')` res := ts.sqlExecutor.ExecuteStatements(session, query, nil) defer res.Close() if a, e := len(res.ResultList), 1; a != e { t.Fatalf("len(results) %d != %d", a, e) } else if res.ResultList[0].Err != nil { t.Fatal(res.ResultList[0].Err) } // Query the API for users. var resp serverpb.UsersResponse if err := getAdminJSONProto(s, "users", &resp); err != nil { t.Fatal(err) } expResult := serverpb.UsersResponse{ Users: []serverpb.UsersResponse_User{ {Username: "******"}, {Username: "******"}, }, } // Verify results. const sortKey = "Username" testutils.SortStructs(resp.Users, sortKey) testutils.SortStructs(expResult.Users, sortKey) if !reflect.DeepEqual(resp, expResult) { t.Fatalf("result %v != expected %v", resp, expResult) } }
func TestAdminAPIEvents(t *testing.T) { defer leaktest.AfterTest(t)() s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) defer s.Stopper().Stop() ts := s.(*TestServer) ac := log.AmbientContext{Tracer: tracing.NewTracer()} ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") defer span.Finish() session := sql.NewSession( ctx, sql.SessionArgs{User: security.RootUser}, ts.sqlExecutor, nil, &sql.MemoryMetrics{}) session.StartUnlimitedMonitor() defer session.Finish(ts.sqlExecutor) setupQueries := []string{ "CREATE DATABASE api_test", "CREATE TABLE api_test.tbl1 (a INT)", "CREATE TABLE api_test.tbl2 (a INT)", "CREATE TABLE api_test.tbl3 (a INT)", "DROP TABLE api_test.tbl1", "DROP TABLE api_test.tbl2", } for _, q := range setupQueries { res := ts.sqlExecutor.ExecuteStatements(session, q, nil) defer res.Close() if res.ResultList[0].Err != nil { t.Fatalf("error executing '%s': %s", q, res.ResultList[0].Err) } } var zeroTimestamp serverpb.EventsResponse_Event_Timestamp testcases := []struct { eventType sql.EventLogType expCount int }{ {"", 7}, {sql.EventLogNodeJoin, 1}, {sql.EventLogNodeRestart, 0}, {sql.EventLogDropDatabase, 0}, {sql.EventLogCreateDatabase, 1}, {sql.EventLogDropTable, 2}, {sql.EventLogCreateTable, 3}, } for i, tc := range testcases { url := "events" if len(tc.eventType) > 0 { url += "?type=" + string(tc.eventType) } var resp serverpb.EventsResponse if err := getAdminJSONProto(s, url, &resp); err != nil { t.Fatal(err) } if a, e := len(resp.Events), tc.expCount; a != e { t.Errorf("%d: # of events %d != expected %d", i, a, e) } // Ensure we don't have blank / nonsensical fields. for _, e := range resp.Events { if e.Timestamp == zeroTimestamp { t.Errorf("%d: missing/empty timestamp", i) } if len(tc.eventType) > 0 { if a, e := e.EventType, string(tc.eventType); a != e { t.Errorf("%d: event type %s != expected %s", i, a, e) } } else { if len(e.EventType) == 0 { t.Errorf("%d: missing event type in event", i) } } if e.TargetID == 0 { t.Errorf("%d: missing/empty TargetID", i) } if e.ReportingID == 0 { t.Errorf("%d: missing/empty ReportingID", i) } if len(e.Info) == 0 { t.Errorf("%d: missing/empty Info", i) } if len(e.UniqueID) == 0 { t.Errorf("%d: missing/empty UniqueID", i) } } } }
// TestAdminAPIZoneDetails verifies the zone configuration information returned // for both DatabaseDetailsResponse AND TableDetailsResponse. func TestAdminAPIZoneDetails(t *testing.T) { defer leaktest.AfterTest(t)() s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) defer s.Stopper().Stop() ts := s.(*TestServer) // Create database and table. ac := log.AmbientContext{Tracer: tracing.NewTracer()} ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") defer span.Finish() session := sql.NewSession( ctx, sql.SessionArgs{User: security.RootUser}, ts.sqlExecutor, nil, &sql.MemoryMetrics{}) session.StartUnlimitedMonitor() setupQueries := []string{ "CREATE DATABASE test", "CREATE TABLE test.tbl (val STRING)", } for _, q := range setupQueries { res := ts.sqlExecutor.ExecuteStatements(session, q, nil) defer res.Close() if res.ResultList[0].Err != nil { t.Fatalf("error executing '%s': %s", q, res.ResultList[0].Err) } } // Function to verify the zone for table "test.tbl" as returned by the Admin // API. verifyTblZone := func( expectedZone config.ZoneConfig, expectedLevel serverpb.ZoneConfigurationLevel, ) { var resp serverpb.TableDetailsResponse if err := getAdminJSONProto(s, "databases/test/tables/tbl", &resp); err != nil { t.Fatal(err) } if a, e := &resp.ZoneConfig, &expectedZone; !proto.Equal(a, e) { t.Errorf("actual table zone config %v did not match expected value %v", a, e) } if a, e := resp.ZoneConfigLevel, expectedLevel; a != e { t.Errorf("actual table ZoneConfigurationLevel %s did not match expected value %s", a, e) } if t.Failed() { t.FailNow() } } // Function to verify the zone for database "test" as returned by the Admin // API. verifyDbZone := func( expectedZone config.ZoneConfig, expectedLevel serverpb.ZoneConfigurationLevel, ) { var resp serverpb.DatabaseDetailsResponse if err := getAdminJSONProto(s, "databases/test", &resp); err != nil { t.Fatal(err) } if a, e := &resp.ZoneConfig, &expectedZone; !proto.Equal(a, e) { t.Errorf("actual db zone config %v did not match expected value %v", a, e) } if a, e := resp.ZoneConfigLevel, expectedLevel; a != e { t.Errorf("actual db ZoneConfigurationLevel %s did not match expected value %s", a, e) } if t.Failed() { t.FailNow() } } // Function to store a zone config for a given object ID. setZone := func(zoneCfg config.ZoneConfig, id sqlbase.ID) { zoneBytes, err := zoneCfg.Marshal() if err != nil { t.Fatal(err) } const query = `INSERT INTO system.zones VALUES($1, $2)` params := parser.NewPlaceholderInfo() params.SetValue(`1`, parser.NewDInt(parser.DInt(id))) params.SetValue(`2`, parser.NewDBytes(parser.DBytes(zoneBytes))) res := ts.sqlExecutor.ExecuteStatements(session, query, params) defer res.Close() if res.ResultList[0].Err != nil { t.Fatalf("error executing '%s': %s", query, res.ResultList[0].Err) } } // Verify zone matches cluster default. verifyDbZone(config.DefaultZoneConfig(), serverpb.ZoneConfigurationLevel_CLUSTER) verifyTblZone(config.DefaultZoneConfig(), serverpb.ZoneConfigurationLevel_CLUSTER) // Get ID path for table. This will be an array of three IDs, containing the ID of the root namespace, // the database, and the table (in that order). idPath, err := ts.admin.queryDescriptorIDPath(session, []string{"test", "tbl"}) if err != nil { t.Fatal(err) } // Apply zone configuration to database and check again. dbZone := config.ZoneConfig{ RangeMinBytes: 456, } setZone(dbZone, idPath[1]) verifyDbZone(dbZone, serverpb.ZoneConfigurationLevel_DATABASE) verifyTblZone(dbZone, serverpb.ZoneConfigurationLevel_DATABASE) // Apply zone configuration to table and check again. tblZone := config.ZoneConfig{ RangeMinBytes: 789, } setZone(tblZone, idPath[2]) verifyDbZone(dbZone, serverpb.ZoneConfigurationLevel_DATABASE) verifyTblZone(tblZone, serverpb.ZoneConfigurationLevel_TABLE) }
func testAdminAPITableDetailsInner(t *testing.T, dbName, tblName string) { s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) defer s.Stopper().Stop() ts := s.(*TestServer) escDBName := parser.Name(dbName).String() escTblName := parser.Name(tblName).String() ac := log.AmbientContext{Tracer: tracing.NewTracer()} ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") defer span.Finish() session := sql.NewSession( ctx, sql.SessionArgs{User: security.RootUser}, ts.sqlExecutor, nil, &sql.MemoryMetrics{}) session.StartUnlimitedMonitor() defer session.Finish(ts.sqlExecutor) setupQueries := []string{ fmt.Sprintf("CREATE DATABASE %s", escDBName), fmt.Sprintf(`CREATE TABLE %s.%s ( nulls_allowed INT, nulls_not_allowed INT NOT NULL DEFAULT 1000, default2 INT DEFAULT 2, string_default STRING DEFAULT 'default_string' )`, escDBName, escTblName), fmt.Sprintf("GRANT SELECT ON %s.%s TO readonly", escDBName, escTblName), fmt.Sprintf("GRANT SELECT,UPDATE,DELETE ON %s.%s TO app", escDBName, escTblName), fmt.Sprintf("CREATE INDEX descIdx ON %s.%s (default2 DESC)", escDBName, escTblName), } for _, q := range setupQueries { res := ts.sqlExecutor.ExecuteStatements(session, q, nil) defer res.Close() if res.ResultList[0].Err != nil { t.Fatalf("error executing '%s': %s", q, res.ResultList[0].Err) } } // Perform API call. var resp serverpb.TableDetailsResponse url := fmt.Sprintf("databases/%s/tables/%s", dbName, tblName) if err := getAdminJSONProto(s, url, &resp); err != nil { t.Fatal(err) } // Verify columns. expColumns := []serverpb.TableDetailsResponse_Column{ {Name: "nulls_allowed", Type: "INT", Nullable: true, DefaultValue: ""}, {Name: "nulls_not_allowed", Type: "INT", Nullable: false, DefaultValue: "1000"}, {Name: "default2", Type: "INT", Nullable: true, DefaultValue: "2"}, {Name: "string_default", Type: "STRING", Nullable: true, DefaultValue: "'default_string'"}, {Name: "rowid", Type: "INT", Nullable: false, DefaultValue: "unique_rowid()"}, } testutils.SortStructs(expColumns, "Name") testutils.SortStructs(resp.Columns, "Name") if a, e := len(resp.Columns), len(expColumns); a != e { t.Fatalf("# of result columns %d != expected %d (got: %#v)", a, e, resp.Columns) } for i, a := range resp.Columns { e := expColumns[i] if a.String() != e.String() { t.Fatalf("mismatch at column %d: actual %#v != %#v", i, a, e) } } // Verify grants. expGrants := []serverpb.TableDetailsResponse_Grant{ {User: security.RootUser, Privileges: []string{"ALL"}}, {User: "******", Privileges: []string{"DELETE", "SELECT", "UPDATE"}}, {User: "******", Privileges: []string{"SELECT"}}, } testutils.SortStructs(expGrants, "User") testutils.SortStructs(resp.Grants, "User") if a, e := len(resp.Grants), len(expGrants); a != e { t.Fatalf("# of grant columns %d != expected %d (got: %#v)", a, e, resp.Grants) } for i, a := range resp.Grants { e := expGrants[i] sort.Strings(a.Privileges) sort.Strings(e.Privileges) if a.String() != e.String() { t.Fatalf("mismatch at index %d: actual %#v != %#v", i, a, e) } } // Verify indexes. expIndexes := []serverpb.TableDetailsResponse_Index{ {Name: "primary", Column: "rowid", Direction: "ASC", Unique: true, Seq: 1}, {Name: "descIdx", Column: "default2", Direction: "DESC", Unique: false, Seq: 1}, } testutils.SortStructs(expIndexes, "Column") testutils.SortStructs(resp.Indexes, "Column") for i, a := range resp.Indexes { e := expIndexes[i] if a.String() != e.String() { t.Fatalf("mismatch at index %d: actual %#v != %#v", i, a, e) } } // Verify range count. if a, e := resp.RangeCount, int64(1); a != e { t.Fatalf("# of ranges %d != expected %d", a, e) } // Verify Create Table Statement. { const createTableCol = "CreateTable" showCreateTableQuery := fmt.Sprintf("SHOW CREATE TABLE %s.%s", escDBName, escTblName) resSet := ts.sqlExecutor.ExecuteStatements(session, showCreateTableQuery, nil) defer resSet.Close() res := resSet.ResultList[0] if res.Err != nil { t.Fatalf("error executing '%s': %s", showCreateTableQuery, res.Err) } scanner := makeResultScanner(res.Columns) var createStmt string if err := scanner.Scan(res.Rows.At(0), createTableCol, &createStmt); err != nil { t.Fatal(err) } if a, e := resp.CreateTableStatement, createStmt; a != e { t.Fatalf("mismatched create table statement; expected %s, got %s", e, a) } } // Verify Descriptor ID. path, err := ts.admin.queryDescriptorIDPath(session, []string{dbName, tblName}) if err != nil { t.Fatal(err) } if a, e := resp.DescriptorID, int64(path[2]); a != e { t.Fatalf("table had descriptorID %d, expected %d", a, e) } }
func TestAdminAPIDatabases(t *testing.T) { defer leaktest.AfterTest(t)() s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) defer s.Stopper().Stop() ts := s.(*TestServer) ac := log.AmbientContext{Tracer: tracing.NewTracer()} ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") defer span.Finish() // Test databases endpoint. const testdb = "test" session := sql.NewSession( ctx, sql.SessionArgs{User: security.RootUser}, ts.sqlExecutor, nil, &sql.MemoryMetrics{}) session.StartUnlimitedMonitor() defer session.Finish(ts.sqlExecutor) query := "CREATE DATABASE " + testdb createRes := ts.sqlExecutor.ExecuteStatements(session, query, nil) defer createRes.Close() if createRes.ResultList[0].Err != nil { t.Fatal(createRes.ResultList[0].Err) } var resp serverpb.DatabasesResponse if err := getAdminJSONProto(s, "databases", &resp); err != nil { t.Fatal(err) } expectedDBs := []string{"system", testdb} if a, e := len(resp.Databases), len(expectedDBs); a != e { t.Fatalf("length of result %d != expected %d", a, e) } sort.Strings(resp.Databases) for i, e := range expectedDBs { if a := resp.Databases[i]; a != e { t.Fatalf("database name %s != expected %s", a, e) } } // Test database details endpoint. privileges := []string{"SELECT", "UPDATE"} testuser := "******" grantQuery := "GRANT " + strings.Join(privileges, ", ") + " ON DATABASE " + testdb + " TO " + testuser grantRes := s.(*TestServer).sqlExecutor.ExecuteStatements(session, grantQuery, nil) defer grantRes.Close() if grantRes.ResultList[0].Err != nil { t.Fatal(grantRes.ResultList[0].Err) } var details serverpb.DatabaseDetailsResponse if err := getAdminJSONProto(s, "databases/"+testdb, &details); err != nil { t.Fatal(err) } if a, e := len(details.Grants), 2; a != e { t.Fatalf("# of grants %d != expected %d", a, e) } for _, grant := range details.Grants { switch grant.User { case security.RootUser: if !reflect.DeepEqual(grant.Privileges, []string{"ALL"}) { t.Fatalf("privileges %v != expected %v", details.Grants[0].Privileges, privileges) } case testuser: sort.Strings(grant.Privileges) if !reflect.DeepEqual(grant.Privileges, privileges) { t.Fatalf("privileges %v != expected %v", grant.Privileges, privileges) } default: t.Fatalf("unknown grant to user %s", grant.User) } } // Verify Descriptor ID. path, err := ts.admin.queryDescriptorIDPath(session, []string{testdb}) if err != nil { t.Fatal(err) } if a, e := details.DescriptorID, int64(path[1]); a != e { t.Fatalf("db had descriptorID %d, expected %d", a, e) } }
func TestAdminAPITableDetailsForVirtualSchema(t *testing.T) { defer leaktest.AfterTest(t)() s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) defer s.Stopper().Stop() ts := s.(*TestServer) // Perform API call. var resp serverpb.TableDetailsResponse if err := getAdminJSONProto(s, "databases/information_schema/tables/schemata", &resp); err != nil { t.Fatal(err) } // Verify columns. expColumns := []serverpb.TableDetailsResponse_Column{ {Name: "CATALOG_NAME", Type: "STRING", Nullable: false, DefaultValue: "''"}, {Name: "SCHEMA_NAME", Type: "STRING", Nullable: false, DefaultValue: "''"}, {Name: "DEFAULT_CHARACTER_SET_NAME", Type: "STRING", Nullable: false, DefaultValue: "''"}, {Name: "SQL_PATH", Type: "STRING", Nullable: true}, } testutils.SortStructs(expColumns, "Name") testutils.SortStructs(resp.Columns, "Name") if a, e := len(resp.Columns), len(expColumns); a != e { t.Fatalf("# of result columns %d != expected %d (got: %#v)", a, e, resp.Columns) } for i, a := range resp.Columns { e := expColumns[i] if a.String() != e.String() { t.Fatalf("mismatch at column %d: actual %#v != %#v", i, a, e) } } // Verify grants. if a, e := len(resp.Grants), 0; a != e { t.Fatalf("# of grant columns %d != expected %d (got: %#v)", a, e, resp.Grants) } // Verify indexes. if a, e := resp.RangeCount, int64(0); a != e { t.Fatalf("# of indexes %d != expected %d", a, e) } // Verify range count. if a, e := resp.RangeCount, int64(0); a != e { t.Fatalf("# of ranges %d != expected %d", a, e) } // Verify Create Table Statement. { const ( showCreateTableQuery = "SHOW CREATE TABLE information_schema.schemata" createTableCol = "CreateTable" ) ac := log.AmbientContext{Tracer: tracing.NewTracer()} ctx, span := ac.AnnotateCtxWithSpan(context.Background(), "test") defer span.Finish() session := sql.NewSession( ctx, sql.SessionArgs{User: security.RootUser}, ts.sqlExecutor, nil, &sql.MemoryMetrics{}) session.StartUnlimitedMonitor() defer session.Finish(ts.sqlExecutor) resSet := ts.sqlExecutor.ExecuteStatements(session, showCreateTableQuery, nil) defer resSet.Close() res := resSet.ResultList[0] if res.Err != nil { t.Fatalf("error executing '%s': %s", showCreateTableQuery, res.Err) } scanner := makeResultScanner(res.Columns) var createStmt string if err := scanner.Scan(res.Rows.At(0), createTableCol, &createStmt); err != nil { t.Fatal(err) } if a, e := resp.CreateTableStatement, createStmt; a != e { t.Fatalf("mismatched create table statement; expected %s, got %s", e, a) } } }