// Start starts the test cluster by bootstrapping an in-memory store // (defaults to maximum of 50M). The server is started, launching the // node RPC server and all HTTP endpoints. Use the value of // TestServer.Addr after Start() for client connections. Use Stop() // to shutdown the server after the test completes. func (ltc *LocalTestCluster) Start(t util.Tester, baseCtx *base.Config, initSender InitSenderFn) { ambient := log.AmbientContext{Tracer: tracing.NewTracer()} nc := &base.NodeIDContainer{} ambient.AddLogTag("n", nc) nodeID := roachpb.NodeID(1) nodeDesc := &roachpb.NodeDescriptor{NodeID: nodeID} ltc.tester = t ltc.Manual = hlc.NewManualClock(0) ltc.Clock = hlc.NewClock(ltc.Manual.UnixNano) ltc.Stopper = stop.NewStopper() rpcContext := rpc.NewContext(ambient, baseCtx, ltc.Clock, ltc.Stopper) server := rpc.NewServer(rpcContext) // never started ltc.Gossip = gossip.New(ambient, nc, rpcContext, server, nil, ltc.Stopper, metric.NewRegistry()) ltc.Eng = engine.NewInMem(roachpb.Attributes{}, 50<<20) ltc.Stopper.AddCloser(ltc.Eng) ltc.Stores = storage.NewStores(ambient, ltc.Clock) ltc.Sender = initSender(nodeDesc, ambient.Tracer, ltc.Clock, ltc.Latency, ltc.Stores, ltc.Stopper, ltc.Gossip) if ltc.DBContext == nil { dbCtx := client.DefaultDBContext() ltc.DBContext = &dbCtx } ltc.DB = client.NewDBWithContext(ltc.Sender, *ltc.DBContext) transport := storage.NewDummyRaftTransport() cfg := storage.TestStoreConfig() if ltc.RangeRetryOptions != nil { cfg.RangeRetryOptions = *ltc.RangeRetryOptions } cfg.AmbientCtx = ambient cfg.Clock = ltc.Clock cfg.DB = ltc.DB cfg.Gossip = ltc.Gossip cfg.Transport = transport cfg.MetricsSampleInterval = metric.TestSampleInterval ltc.Store = storage.NewStore(cfg, ltc.Eng, nodeDesc) if err := ltc.Store.Bootstrap(roachpb.StoreIdent{NodeID: nodeID, StoreID: 1}); err != nil { t.Fatalf("unable to start local test cluster: %s", err) } ltc.Stores.AddStore(ltc.Store) if err := ltc.Store.BootstrapRange(nil); err != nil { t.Fatalf("unable to start local test cluster: %s", err) } if err := ltc.Store.Start(context.Background(), ltc.Stopper); err != nil { t.Fatalf("unable to start local test cluster: %s", err) } nc.Set(context.TODO(), nodeDesc.NodeID) if err := ltc.Gossip.SetNodeDescriptor(nodeDesc); err != nil { t.Fatalf("unable to set node descriptor: %s", err) } }
// MakeServer instantiates a new Server which services requests with data from // the supplied DB. func MakeServer(ambient log.AmbientContext, db *DB, cfg ServerConfig, stopper *stop.Stopper) Server { ambient.AddLogTag("ts-srv", nil) queryWorkerMax := queryWorkerMax if cfg.QueryWorkerMax != 0 { queryWorkerMax = cfg.QueryWorkerMax } return Server{ AmbientContext: ambient, db: db, stopper: stopper, workerSem: make(chan struct{}, queryWorkerMax), } }
// New creates an instance of a gossip node. // The higher level manages the NodeIDContainer instance (which can be shared by // various server components). The ambient context is expected to already // contain the node ID. func New( ambient log.AmbientContext, nodeID *base.NodeIDContainer, rpcContext *rpc.Context, grpcServer *grpc.Server, resolvers []resolver.Resolver, stopper *stop.Stopper, registry *metric.Registry, ) *Gossip { ambient.SetEventLog("gossip", "gossip") g := &Gossip{ server: newServer(ambient, nodeID, stopper, registry), Connected: make(chan struct{}), rpcContext: rpcContext, outgoing: makeNodeSet(minPeers, metric.NewGauge(MetaConnectionsOutgoingGauge)), bootstrapping: map[string]struct{}{}, disconnected: make(chan *client, 10), stalledCh: make(chan struct{}, 1), stallInterval: defaultStallInterval, bootstrapInterval: defaultBootstrapInterval, cullInterval: defaultCullInterval, nodeDescs: map[roachpb.NodeID]*roachpb.NodeDescriptor{}, resolverAddrs: map[util.UnresolvedAddr]resolver.Resolver{}, bootstrapAddrs: map[util.UnresolvedAddr]roachpb.NodeID{}, } stopper.AddCloser(stop.CloserFn(g.server.AmbientContext.FinishEventLog)) registry.AddMetric(g.outgoing.gauge) g.clientsMu.breakers = map[string]*circuit.Breaker{} resolverAddrs := make([]string, len(resolvers)) for i, resolver := range resolvers { resolverAddrs[i] = resolver.Addr() } ctx := g.AnnotateCtx(context.Background()) if log.V(1) { log.Infof(ctx, "initial resolvers: %v", resolverAddrs) } g.SetResolvers(resolvers) g.mu.Lock() // Add ourselves as a SystemConfig watcher. g.mu.is.registerCallback(KeySystemConfig, g.updateSystemConfig) // Add ourselves as a node descriptor watcher. g.mu.is.registerCallback(MakePrefixPattern(KeyNodeIDPrefix), g.updateNodeAddress) g.mu.Unlock() RegisterGossipServer(grpcServer, g.server) return g }
// NewTest is a simplified wrapper around New that creates the NodeIDContainer // internally. Used for testing. func NewTest( nodeID roachpb.NodeID, rpcContext *rpc.Context, grpcServer *grpc.Server, resolvers []resolver.Resolver, stopper *stop.Stopper, registry *metric.Registry, ) *Gossip { n := &base.NodeIDContainer{} var ac log.AmbientContext ac.AddLogTag("n", n) gossip := New(ac, n, rpcContext, grpcServer, resolvers, stopper, registry) if nodeID != 0 { n.Set(context.TODO(), nodeID) } return gossip }
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) } }
// PollSource begins a Goroutine which periodically queries the supplied // DataSource for time series data, storing the returned data in the server. // Stored data will be sampled using the provided Resolution. The polling // process will continue until the provided stop.Stopper is stopped. func (db *DB) PollSource( ambient log.AmbientContext, source DataSource, frequency time.Duration, r Resolution, stopper *stop.Stopper, ) { ambient.AddLogTag("ts-poll", nil) p := &poller{ AmbientContext: ambient, db: db, source: source, frequency: frequency, r: r, stopper: stopper, } p.start() }
// NewContext creates an rpc Context with the supplied values. func NewContext( ambient log.AmbientContext, baseCtx *base.Config, hlcClock *hlc.Clock, stopper *stop.Stopper, ) *Context { ctx := &Context{ Config: baseCtx, } if hlcClock != nil { ctx.localClock = hlcClock } else { ctx.localClock = hlc.NewClock(hlc.UnixNano) } ctx.breakerClock = breakerClock{ clock: ctx.localClock, } var cancel context.CancelFunc ctx.masterCtx, cancel = context.WithCancel(ambient.AnnotateCtx(context.Background())) ctx.Stopper = stopper ctx.RemoteClocks = newRemoteClockMonitor( ctx.masterCtx, ctx.localClock, 10*defaultHeartbeatInterval) ctx.HeartbeatInterval = defaultHeartbeatInterval ctx.HeartbeatTimeout = 2 * defaultHeartbeatInterval ctx.conns.cache = make(map[string]*connMeta) stopper.RunWorker(func() { <-stopper.ShouldQuiesce() cancel() ctx.conns.Lock() for key, meta := range ctx.conns.cache { meta.Do(func() { // Make sure initialization is not in progress when we're removing the // conn. We need to set the error in case we win the race against the // real initialization code. if meta.err == nil { meta.err = &roachpb.NodeUnavailableError{} } }) ctx.removeConnLocked(key, meta) } ctx.conns.Unlock() }) return ctx }
// newStatusServer allocates and returns a statusServer. func newStatusServer( ambient log.AmbientContext, db *client.DB, gossip *gossip.Gossip, metricSource metricMarshaler, rpcCtx *rpc.Context, stores *storage.Stores, ) *statusServer { ambient.AddLogTag("status", nil) server := &statusServer{ AmbientContext: ambient, db: db, gossip: gossip, metricSource: metricSource, rpcCtx: rpcCtx, stores: stores, } return server }
func newRaftScheduler( ambient log.AmbientContext, metrics *StoreMetrics, processor raftProcessor, numWorkers int, ) *raftScheduler { s := &raftScheduler{ processor: processor, numWorkers: numWorkers, } muLogger := syncutil.ThresholdLogger( ambient.AnnotateCtx(context.Background()), defaultReplicaMuWarnThreshold, func(ctx context.Context, msg string, args ...interface{}) { log.Warningf(ctx, "raftScheduler.mu: "+msg, args...) }, func(t time.Duration) { if metrics != nil { metrics.MuSchedulerNanos.RecordValue(t.Nanoseconds()) } }, ) s.mu.TimedMutex = syncutil.MakeTimedMutex(muLogger) s.mu.cond = sync.NewCond(&s.mu.TimedMutex) s.mu.state = make(map[roachpb.RangeID]raftScheduleState) return s }
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) } } }