// NewQueryEngine creates a new QueryEngine. // This is a singleton class. // You must call this only once. func NewQueryEngine(checker MySQLChecker, config Config) *QueryEngine { qe := &QueryEngine{config: config} qe.queryServiceStats = NewQueryServiceStats(config.StatsPrefix, config.EnablePublishStats) qe.schemaInfo = NewSchemaInfo( config.StatsPrefix, checker, config.QueryCacheSize, time.Duration(config.SchemaReloadTime*1e9), time.Duration(config.IdleTimeout*1e9), map[string]string{ debugQueryPlansKey: config.DebugURLPrefix + "/query_plans", debugQueryStatsKey: config.DebugURLPrefix + "/query_stats", debugSchemaKey: config.DebugURLPrefix + "/schema", debugQueryRulesKey: config.DebugURLPrefix + "/query_rules", }, config.EnablePublishStats, qe.queryServiceStats, ) qe.connPool = NewConnPool( config.PoolNamePrefix+"ConnPool", config.PoolSize, time.Duration(config.IdleTimeout*1e9), config.EnablePublishStats, qe.queryServiceStats, checker, ) qe.streamConnPool = NewConnPool( config.PoolNamePrefix+"StreamConnPool", config.StreamPoolSize, time.Duration(config.IdleTimeout*1e9), config.EnablePublishStats, qe.queryServiceStats, checker, ) qe.txPool = NewTxPool( config.PoolNamePrefix+"TransactionPool", config.StatsPrefix, config.TransactionCap, time.Duration(config.TransactionTimeout*1e9), time.Duration(config.IdleTimeout*1e9), config.EnablePublishStats, qe.queryServiceStats, checker, ) // Set the prepared pool capacity to something lower than // tx pool capacity. Those spare connections are needed to // perform metadata state change operations. Without this, // the system can deadlock if all connections get moved to // the TxPreparedPool. prepCap := config.TransactionCap - 2 if prepCap < 0 { // A capacity of 0 means that Prepare will always fail. prepCap = 0 } qe.preparedPool = NewTxPreparedPool(prepCap) qe.twoPC = NewTwoPC() qe.consolidator = sync2.NewConsolidator() http.Handle(config.DebugURLPrefix+"/consolidations", qe.consolidator) qe.streamQList = NewQueryList() if config.StrictMode { qe.strictMode.Set(1) } if config.EnableAutoCommit { qe.autoCommit.Set(1) } qe.strictTableAcl = config.StrictTableAcl qe.enableTableAclDryRun = config.EnableTableAclDryRun if config.TableAclExemptACL != "" { if f, err := tableacl.GetCurrentAclFactory(); err == nil { if exemptACL, err := f.New([]string{config.TableAclExemptACL}); err == nil { log.Infof("Setting Table ACL exempt rule for %v", config.TableAclExemptACL) qe.exemptACL = exemptACL } else { log.Infof("Cannot build exempt ACL for table ACL: %v", err) } } else { log.Infof("Cannot get current ACL Factory: %v", err) } } qe.maxResultSize = sync2.NewAtomicInt64(int64(config.MaxResultSize)) qe.maxDMLRows = sync2.NewAtomicInt64(int64(config.MaxDMLRows)) qe.streamBufferSize = sync2.NewAtomicInt64(int64(config.StreamBufferSize)) qe.accessCheckerLogger = logutil.NewThrottledLogger("accessChecker", 1*time.Second) var tableACLAllowedName string var tableACLDeniedName string var tableACLPseudoDeniedName string if config.EnablePublishStats { stats.Publish(config.StatsPrefix+"MaxResultSize", stats.IntFunc(qe.maxResultSize.Get)) stats.Publish(config.StatsPrefix+"MaxDMLRows", stats.IntFunc(qe.maxDMLRows.Get)) stats.Publish(config.StatsPrefix+"StreamBufferSize", stats.IntFunc(qe.streamBufferSize.Get)) stats.Publish(config.StatsPrefix+"TableACLExemptCount", stats.IntFunc(qe.tableaclExemptCount.Get)) tableACLAllowedName = "TableACLAllowed" tableACLDeniedName = "TableACLDenied" tableACLPseudoDeniedName = "TableACLPseudoDenied" } qe.tableaclAllowed = stats.NewMultiCounters(tableACLAllowedName, []string{"TableName", "TableGroup", "PlanID", "Username"}) qe.tableaclDenied = stats.NewMultiCounters(tableACLDeniedName, []string{"TableName", "TableGroup", "PlanID", "Username"}) qe.tableaclPseudoDenied = stats.NewMultiCounters(tableACLPseudoDeniedName, []string{"TableName", "TableGroup", "PlanID", "Username"}) return qe }
// NewQueryEngine creates a new QueryEngine. // This is a singleton class. // You must call this only once. func NewQueryEngine(checker MySQLChecker, config Config) *QueryEngine { qe := &QueryEngine{config: config} qe.queryServiceStats = NewQueryServiceStats(config.StatsPrefix, config.EnablePublishStats) qe.schemaInfo = NewSchemaInfo( config.StatsPrefix, checker, config.QueryCacheSize, time.Duration(config.SchemaReloadTime*1e9), time.Duration(config.IdleTimeout*1e9), map[string]string{ debugQueryPlansKey: config.DebugURLPrefix + "/query_plans", debugQueryStatsKey: config.DebugURLPrefix + "/query_stats", debugSchemaKey: config.DebugURLPrefix + "/schema", debugQueryRulesKey: config.DebugURLPrefix + "/query_rules", }, config.EnablePublishStats, qe.queryServiceStats, ) qe.connPool = NewConnPool( config.PoolNamePrefix+"ConnPool", config.PoolSize, time.Duration(config.IdleTimeout*1e9), config.EnablePublishStats, qe.queryServiceStats, checker, ) qe.streamConnPool = NewConnPool( config.PoolNamePrefix+"StreamConnPool", config.StreamPoolSize, time.Duration(config.IdleTimeout*1e9), config.EnablePublishStats, qe.queryServiceStats, checker, ) qe.txPool = NewTxPool( config.PoolNamePrefix+"TransactionPool", config.StatsPrefix, config.TransactionCap, time.Duration(config.TransactionTimeout*1e9), time.Duration(config.IdleTimeout*1e9), config.EnablePublishStats, qe.queryServiceStats, checker, ) qe.consolidator = sync2.NewConsolidator() http.Handle(config.DebugURLPrefix+"/consolidations", qe.consolidator) qe.streamQList = NewQueryList() if config.StrictMode { qe.strictMode.Set(1) } if config.EnableAutoCommit { qe.autoCommit.Set(1) } qe.strictTableAcl = config.StrictTableAcl qe.enableTableAclDryRun = config.EnableTableAclDryRun if config.TableAclExemptACL != "" { if f, err := tableacl.GetCurrentAclFactory(); err == nil { if exemptACL, err := f.New([]string{config.TableAclExemptACL}); err == nil { log.Infof("Setting Table ACL exempt rule for %v", config.TableAclExemptACL) qe.exemptACL = exemptACL } else { log.Infof("Cannot build exempt ACL for table ACL: %v", err) } } else { log.Infof("Cannot get current ACL Factory: %v", err) } } qe.maxResultSize = sync2.NewAtomicInt64(int64(config.MaxResultSize)) qe.maxDMLRows = sync2.NewAtomicInt64(int64(config.MaxDMLRows)) qe.streamBufferSize = sync2.NewAtomicInt64(int64(config.StreamBufferSize)) qe.accessCheckerLogger = logutil.NewThrottledLogger("accessChecker", 1*time.Second) var tableACLAllowedName string var tableACLDeniedName string var tableACLPseudoDeniedName string if config.EnablePublishStats { stats.Publish(config.StatsPrefix+"MaxResultSize", stats.IntFunc(qe.maxResultSize.Get)) stats.Publish(config.StatsPrefix+"MaxDMLRows", stats.IntFunc(qe.maxDMLRows.Get)) stats.Publish(config.StatsPrefix+"StreamBufferSize", stats.IntFunc(qe.streamBufferSize.Get)) stats.Publish(config.StatsPrefix+"TableACLExemptCount", stats.IntFunc(qe.tableaclExemptCount.Get)) tableACLAllowedName = "TableACLAllowed" tableACLDeniedName = "TableACLDenied" tableACLPseudoDeniedName = "TableACLPseudoDenied" } qe.tableaclAllowed = stats.NewMultiCounters(tableACLAllowedName, []string{"TableName", "TableGroup", "PlanID", "Username"}) qe.tableaclDenied = stats.NewMultiCounters(tableACLDeniedName, []string{"TableName", "TableGroup", "PlanID", "Username"}) qe.tableaclPseudoDenied = stats.NewMultiCounters(tableACLPseudoDeniedName, []string{"TableName", "TableGroup", "PlanID", "Username"}) return qe }
func TestQueryExecutorTableAclExemptACL(t *testing.T) { aclName := fmt.Sprintf("simpleacl-test-%d", rand.Int63()) tableacl.Register(aclName, &simpleacl.Factory{}) tableacl.SetDefaultACL(aclName) db := setUpQueryExecutorTest() query := "select * from test_table limit 1000" want := &mproto.QueryResult{ Fields: getTestTableFields(), RowsAffected: 0, Rows: [][]sqltypes.Value{}, } db.AddQuery(query, want) db.AddQuery("select * from test_table where 1 != 1", &mproto.QueryResult{ Fields: getTestTableFields(), }) username := "******" callerID := &querypb.VTGateCallerID{ Username: username, } ctx := callerid.NewContext(context.Background(), nil, callerID) config := &tableaclpb.Config{ TableGroups: []*tableaclpb.TableGroupSpec{{ Name: "group02", TableNamesOrPrefixes: []string{"test_table"}, Readers: []string{"u1"}, }}, } if err := tableacl.InitFromProto(config); err != nil { t.Fatalf("unable to load tableacl config, error: %v", err) } // enable Config.StrictTableAcl tsv := newTestTabletServer(ctx, enableRowCache|enableSchemaOverrides|enableStrict|enableStrictTableAcl, db) qre := newTestQueryExecutor(ctx, tsv, query, 0) defer tsv.StopService() checkPlanID(t, planbuilder.PlanPassSelect, qre.plan.PlanID) // query should fail because current user do not have read permissions _, err := qre.Execute() if err == nil { t.Fatal("got: nil, want: error") } tabletError, ok := err.(*TabletError) if !ok { t.Fatalf("got: %v, want: *TabletError", err) } if tabletError.ErrorType != ErrFail { t.Fatalf("got: %s, want: ErrFail", getTabletErrorString(tabletError.ErrorType)) } if !strings.Contains(tabletError.Error(), "table acl error") { t.Fatalf("got %s, want tablet errorL table acl error", tabletError.Error()) } // table acl should be ignored since this is an exempt user. username = "******" f, _ := tableacl.GetCurrentAclFactory() if tsv.qe.exemptACL, err = f.New([]string{username}); err != nil { t.Fatalf("Cannot load exempt ACL for Table ACL: %v", err) } callerID = &querypb.VTGateCallerID{ Username: username, } ctx = callerid.NewContext(context.Background(), nil, callerID) qre = newTestQueryExecutor(ctx, tsv, query, 0) _, err = qre.Execute() if err != nil { t.Fatal("qre.Execute: nil, want: error") } }
func allString() string { return tableacl.GetCurrentAclFactory().AllString() }