blob: f0e06c81b415323396fd8d55aac15d8ad7f0e84c [file] [log] [blame]
// Package builders builds objects from config.InstanceConfig objects.
//
// These are functions separate from config.InstanceConfig so that we don't end
// up with cyclical import issues.
package builders
import (
"context"
"database/sql"
"strings"
"cloud.google.com/go/bigtable"
"cloud.google.com/go/datastore"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/ds"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/perf/go/alerts"
"go.skia.org/infra/perf/go/alerts/dsalertstore"
"go.skia.org/infra/perf/go/alerts/sqlalertstore"
"go.skia.org/infra/perf/go/cid"
"go.skia.org/infra/perf/go/config"
"go.skia.org/infra/perf/go/file"
"go.skia.org/infra/perf/go/file/dirsource"
"go.skia.org/infra/perf/go/file/gcssource"
perfgit "go.skia.org/infra/perf/go/git"
"go.skia.org/infra/perf/go/regression"
"go.skia.org/infra/perf/go/regression/dsregressionstore"
"go.skia.org/infra/perf/go/regression/sqlregressionstore"
"go.skia.org/infra/perf/go/shortcut"
"go.skia.org/infra/perf/go/shortcut/dsshortcutstore"
"go.skia.org/infra/perf/go/shortcut/sqlshortcutstore"
perfsql "go.skia.org/infra/perf/go/sql"
"go.skia.org/infra/perf/go/sql/migrations"
"go.skia.org/infra/perf/go/sql/migrations/cockroachdb"
"go.skia.org/infra/perf/go/tracestore"
"go.skia.org/infra/perf/go/tracestore/btts"
"go.skia.org/infra/perf/go/tracestore/sqltracestore"
"google.golang.org/api/option"
)
// newCockroachDBFromConfig opens an existing CockroachDB database with all
// migrations applied.
func newCockroachDBFromConfig(instanceConfig *config.InstanceConfig) (*sql.DB, error) {
// Note that the migrationsConnection is different from the sql.Open
// connection string since migrations know about CockroachDB, but we use the
// Postgres driver for the database/sql connection since there's no native
// CockroachDB golang driver, and the suggested SQL drive for CockroachDB is
// the Postgres driver since that's the underlying communication protocol it
// uses.
migrationsConnection := strings.Replace(instanceConfig.DataStoreConfig.ConnectionString, "postgresql://", "cockroach://", 1)
db, err := sql.Open("postgres", instanceConfig.DataStoreConfig.ConnectionString)
if err != nil {
return nil, skerr.Wrap(err)
}
cockroachdbMigrations, err := cockroachdb.New()
if err != nil {
return nil, skerr.Wrap(err)
}
err = migrations.Up(cockroachdbMigrations, migrationsConnection)
if err != nil {
return nil, skerr.Wrap(err)
}
sklog.Infof("Finished applying migrations.")
return db, nil
}
// NewPerfGitFromConfig return a new perfgit.Git for the given instanceConfig.
//
// The instance created does not poll by default, callers need to call
// StartBackgroundPolling().
func NewPerfGitFromConfig(ctx context.Context, local bool, instanceConfig *config.InstanceConfig) (*perfgit.Git, error) {
if instanceConfig.DataStoreConfig.ConnectionString == "" {
return nil, skerr.Fmt("A connection_string must always be supplied.")
}
// First figure out what dialect we should use.
var dialect perfsql.Dialect
switch instanceConfig.DataStoreConfig.DataStoreType {
case config.GCPDataStoreType:
if strings.HasPrefix(instanceConfig.DataStoreConfig.ConnectionString, "postgresql://") {
// This is a temporary path as we migrate away from BigTable to
// CockroachDB. The first small step in that migration is to host the
// perfgit Commits table on CockroachDB, which has no analog in the
// "gcs" world.
dialect = perfsql.CockroachDBDialect
} else {
return nil, skerr.Fmt("unknown connection_string: Must begni with postgresql://.")
}
case config.CockroachDBDataStoreType:
dialect = perfsql.CockroachDBDialect
default:
return nil, skerr.Fmt("Unknown datastore_type: %q", instanceConfig.DataStoreConfig.DataStoreType)
}
sklog.Infof("Constructing perfgit with dialect: %q and connection_string: %q", dialect, instanceConfig.DataStoreConfig.ConnectionString)
// Now create the appropriate db.
db, err := newCockroachDBFromConfig(instanceConfig)
if err != nil {
return nil, skerr.Wrap(err)
}
g, err := perfgit.New(ctx, local, db, dialect, instanceConfig)
if err != nil {
return nil, skerr.Wrap(err)
}
return g, nil
}
// NewTraceStoreFromConfig creates a new TraceStore from the InstanceConfig.
//
// If local is true then we aren't running in production.
func NewTraceStoreFromConfig(ctx context.Context, local bool, instanceConfig *config.InstanceConfig) (tracestore.TraceStore, error) {
switch instanceConfig.DataStoreConfig.DataStoreType {
case config.GCPDataStoreType:
ts, err := auth.NewDefaultTokenSource(local, bigtable.Scope)
if err != nil {
return nil, skerr.Wrap(err)
}
traceStore, err := btts.NewBigTableTraceStoreFromConfig(ctx, instanceConfig, ts, false)
if err != nil {
return nil, skerr.Wrapf(err, "Failed to open BigTable trace store.")
}
return traceStore, nil
case config.CockroachDBDataStoreType:
db, err := newCockroachDBFromConfig(instanceConfig)
if err != nil {
return nil, skerr.Wrap(err)
}
return sqltracestore.New(db, perfsql.CockroachDBDialect, instanceConfig.DataStoreConfig)
}
return nil, skerr.Fmt("Unknown datastore type: %q", instanceConfig.DataStoreConfig.DataStoreType)
}
func initCloudDatastoreOnce(ctx context.Context, local bool, instanceConfig *config.InstanceConfig) error {
if ds.DS != nil {
sklog.Infof("Cloud Datastore has already been initialized.")
return nil
}
sklog.Info("About to create token source.")
ts, err := auth.NewDefaultTokenSource(local, datastore.ScopeDatastore)
if err != nil {
return skerr.Wrapf(err, "Failed to get TokenSource")
}
sklog.Info("About to init datastore.")
if err := ds.InitWithOpt(instanceConfig.DataStoreConfig.Project, instanceConfig.DataStoreConfig.Namespace, option.WithTokenSource(ts)); err != nil {
return skerr.Wrapf(err, "Failed to init Cloud Datastore")
}
return nil
}
// NewAlertStoreFromConfig creates a new alerts.Store from the InstanceConfig.
func NewAlertStoreFromConfig(ctx context.Context, local bool, instanceConfig *config.InstanceConfig) (alerts.Store, error) {
switch instanceConfig.DataStoreConfig.DataStoreType {
case config.GCPDataStoreType:
if err := initCloudDatastoreOnce(ctx, local, instanceConfig); err != nil {
return nil, skerr.Wrap(err)
}
return dsalertstore.New(), nil
case config.CockroachDBDataStoreType:
db, err := newCockroachDBFromConfig(instanceConfig)
if err != nil {
return nil, skerr.Wrap(err)
}
return sqlalertstore.New(db, perfsql.CockroachDBDialect)
}
return nil, skerr.Fmt("Unknown datastore type: %q", instanceConfig.DataStoreConfig.DataStoreType)
}
// NewRegressionStoreFromConfig creates a new regression.RegressionStore from
// the InstanceConfig.
//
// If local is true then we aren't running in production.
func NewRegressionStoreFromConfig(ctx context.Context, local bool, cidl *cid.CommitIDLookup, instanceConfig *config.InstanceConfig) (regression.Store, error) {
switch instanceConfig.DataStoreConfig.DataStoreType {
case config.GCPDataStoreType:
if err := initCloudDatastoreOnce(ctx, local, instanceConfig); err != nil {
return nil, skerr.Wrap(err)
}
lookup := func(ctx context.Context, c *cid.CommitID) (*cid.CommitDetail, error) {
details, err := cidl.Lookup(ctx, []*cid.CommitID{c})
if err != nil {
return nil, skerr.Wrap(err)
}
return details[0], nil
}
return dsregressionstore.NewRegressionStoreDS(lookup), nil
case config.CockroachDBDataStoreType:
db, err := newCockroachDBFromConfig(instanceConfig)
if err != nil {
return nil, skerr.Wrap(err)
}
return sqlregressionstore.New(db, perfsql.CockroachDBDialect)
}
return nil, skerr.Fmt("Unknown datastore type: %q", instanceConfig.DataStoreConfig.DataStoreType)
}
// NewShortcutStoreFromConfig creates a new shortcut.Store from the
// InstanceConfig.
func NewShortcutStoreFromConfig(ctx context.Context, local bool, instanceConfig *config.InstanceConfig) (shortcut.Store, error) {
switch instanceConfig.DataStoreConfig.DataStoreType {
case config.GCPDataStoreType:
if err := initCloudDatastoreOnce(ctx, local, instanceConfig); err != nil {
return nil, skerr.Wrap(err)
}
return dsshortcutstore.New(), nil
case config.CockroachDBDataStoreType:
db, err := newCockroachDBFromConfig(instanceConfig)
if err != nil {
return nil, skerr.Wrap(err)
}
return sqlshortcutstore.New(db, perfsql.CockroachDBDialect)
}
return nil, skerr.Fmt("Unknown datastore type: %q", instanceConfig.DataStoreConfig.DataStoreType)
}
// NewSourceFromConfig creates a new file.Source from the InstanceConfig.
//
// If local is true then we aren't running in production.
func NewSourceFromConfig(ctx context.Context, instanceConfig *config.InstanceConfig, local bool) (file.Source, error) {
switch instanceConfig.IngestionConfig.SourceConfig.SourceType {
case config.GCSSourceType:
return gcssource.New(ctx, instanceConfig, local)
case config.DirSourceType:
n := len(instanceConfig.IngestionConfig.SourceConfig.Sources)
if n != 1 {
return nil, skerr.Fmt("For a source_type of 'dir' there must be a single entry for 'sources', found %d.", n)
}
return dirsource.New(instanceConfig.IngestionConfig.SourceConfig.Sources[0])
default:
return nil, skerr.Fmt("Unknown source_type: %q", instanceConfig.IngestionConfig.SourceConfig.SourceType)
}
}