Chore: Add CloudWatch HTTP API tests (#29691)

* CloudWatch: Add HTTP API tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Arve Knudsen
2021-01-07 11:36:13 +01:00
committed by GitHub
parent b6efebd92c
commit 25048ebdf8
33 changed files with 1286 additions and 191 deletions
+5 -5
View File
@@ -1,7 +1,6 @@
package migrator
import (
"errors"
"time"
_ "github.com/go-sql-driver/mysql"
@@ -115,7 +114,8 @@ func (mg *Migrator) Start() error {
}
}
return nil
// Make sure migrations are synced
return mg.x.Sync2()
}
func (mg *Migrator) exec(m Migration, sess *xorm.Session) error {
@@ -126,7 +126,7 @@ func (mg *Migrator) exec(m Migration, sess *xorm.Session) error {
sql, args := condition.SQL(mg.Dialect)
if sql != "" {
mg.Logger.Debug("Executing migration condition sql", "id", m.Id(), "sql", sql, "args", args)
mg.Logger.Debug("Executing migration condition SQL", "id", m.Id(), "sql", sql, "args", args)
results, err := sess.SQL(sql, args...).Query()
if err != nil {
mg.Logger.Error("Executing migration condition failed", "id", m.Id(), "error", err)
@@ -169,8 +169,8 @@ func (mg *Migrator) inTransaction(callback dbTransactionFunc) error {
}
if err := callback(sess); err != nil {
if rollErr := sess.Rollback(); !errors.Is(err, rollErr) {
return errutil.Wrapf(err, "failed to roll back transaction due to error: %s; initial err: %s", rollErr, err)
if rollErr := sess.Rollback(); rollErr != nil {
return errutil.Wrapf(err, "failed to roll back transaction due to error: %s", rollErr)
}
return err
@@ -125,14 +125,18 @@ func (db *MySQLDialect) CleanDB() error {
defer sess.Close()
for _, table := range tables {
if _, err := sess.Exec("set foreign_key_checks = 0"); err != nil {
return errutil.Wrap("failed to disable foreign key checks", err)
}
if _, err := sess.Exec("drop table " + table.Name + " ;"); err != nil {
return errutil.Wrapf(err, "failed to delete table %q", table.Name)
}
if _, err := sess.Exec("set foreign_key_checks = 1"); err != nil {
return errutil.Wrap("failed to disable foreign key checks", err)
switch table.Name {
case "migration_log":
default:
if _, err := sess.Exec("set foreign_key_checks = 0"); err != nil {
return errutil.Wrap("failed to disable foreign key checks", err)
}
if _, err := sess.Exec("drop table " + table.Name + " ;"); err != nil {
return errutil.Wrapf(err, "failed to delete table %q", table.Name)
}
if _, err := sess.Exec("set foreign_key_checks = 1"); err != nil {
return errutil.Wrap("failed to disable foreign key checks", err)
}
}
}
@@ -151,6 +151,7 @@ func (db *PostgresDialect) TruncateDBTables() error {
switch table.Name {
case "":
continue
case "migration_log":
case "dashboard_acl":
// keep default dashboard permissions
if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %v WHERE dashboard_id != -1 AND org_id != -1;", db.Quote(table.Name))); err != nil {
@@ -100,6 +100,7 @@ func (db *SQLite3) TruncateDBTables() error {
for _, table := range tables {
switch table.Name {
case "migration_log":
case "dashboard_acl":
// keep default dashboard permissions
if _, err := sess.Exec(fmt.Sprintf("DELETE FROM %q WHERE dashboard_id != -1 AND org_id != -1;", table.Name)); err != nil {
+94 -14
View File
@@ -1,6 +1,7 @@
package sqlstore
import (
"context"
"fmt"
"time"
@@ -8,9 +9,11 @@ import (
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"xorm.io/xorm"
)
const mainOrgName = "Main Org."
// MainOrgName is the name of the main organization.
const MainOrgName = "Main Org."
func init() {
bus.AddHandler("sql", GetOrgById)
@@ -72,6 +75,20 @@ func GetOrgByName(query *models.GetOrgByNameQuery) error {
return nil
}
// GetOrgByName gets an organization by name.
func (ss *SQLStore) GetOrgByName(name string) (*models.Org, error) {
var org models.Org
exists, err := ss.engine.Where("name=?", name).Get(&org)
if err != nil {
return nil, err
}
if !exists {
return nil, models.ErrOrgNotFound
}
return &org, nil
}
func isOrgNameTaken(name string, existingId int64, sess *DBSession) (bool, error) {
// check if org name is taken
var org models.Org
@@ -88,34 +105,32 @@ func isOrgNameTaken(name string, existingId int64, sess *DBSession) (bool, error
return false, nil
}
func CreateOrg(cmd *models.CreateOrgCommand) error {
return inTransaction(func(sess *DBSession) error {
if isNameTaken, err := isOrgNameTaken(cmd.Name, 0, sess); err != nil {
func createOrg(name string, userID int64, engine *xorm.Engine) (models.Org, error) {
org := models.Org{
Name: name,
Created: time.Now(),
Updated: time.Now(),
}
if err := inTransactionWithRetryCtx(context.Background(), engine, func(sess *DBSession) error {
if isNameTaken, err := isOrgNameTaken(name, 0, sess); err != nil {
return err
} else if isNameTaken {
return models.ErrOrgNameTaken
}
org := models.Org{
Name: cmd.Name,
Created: time.Now(),
Updated: time.Now(),
}
if _, err := sess.Insert(&org); err != nil {
return err
}
user := models.OrgUser{
OrgId: org.Id,
UserId: cmd.UserId,
UserId: userID,
Role: models.ROLE_ADMIN,
Created: time.Now(),
Updated: time.Now(),
}
_, err := sess.Insert(&user)
cmd.Result = org
sess.publishAfterCommit(&events.OrgCreated{
Timestamp: org.Created,
@@ -124,7 +139,26 @@ func CreateOrg(cmd *models.CreateOrgCommand) error {
})
return err
})
}, 0); err != nil {
return org, err
}
return org, nil
}
// CreateOrgWithMember creates an organization with a certain name and a certain user as member.
func (ss *SQLStore) CreateOrgWithMember(name string, userID int64) (models.Org, error) {
return createOrg(name, userID, ss.engine)
}
func CreateOrg(cmd *models.CreateOrgCommand) error {
org, err := createOrg(cmd.Name, cmd.UserId, x)
if err != nil {
return err
}
cmd.Result = org
return nil
}
func UpdateOrg(cmd *models.UpdateOrgCommand) error {
@@ -229,6 +263,52 @@ func verifyExistingOrg(sess *DBSession, orgId int64) error {
return nil
}
func (ss *SQLStore) getOrCreateOrg(sess *DBSession, orgName string) (int64, error) {
var org models.Org
if ss.Cfg.AutoAssignOrg {
has, err := sess.Where("id=?", ss.Cfg.AutoAssignOrgId).Get(&org)
if err != nil {
return 0, err
}
if has {
return org.Id, nil
}
if ss.Cfg.AutoAssignOrgId != 1 {
ss.log.Error("Could not create user: organization ID does not exist", "orgID",
ss.Cfg.AutoAssignOrgId)
return 0, fmt.Errorf("could not create user: organization ID %d does not exist",
ss.Cfg.AutoAssignOrgId)
}
org.Name = MainOrgName
org.Id = int64(ss.Cfg.AutoAssignOrgId)
} else {
org.Name = orgName
}
org.Created = time.Now()
org.Updated = time.Now()
if org.Id != 0 {
if _, err := sess.InsertId(&org); err != nil {
return 0, err
}
} else {
if _, err := sess.InsertOne(&org); err != nil {
return 0, err
}
}
sess.publishAfterCommit(&events.OrgCreated{
Timestamp: org.Created,
Id: org.Id,
Name: org.Name,
})
return org.Id, nil
}
func getOrCreateOrg(sess *DBSession, orgName string) (int64, error) {
var org models.Org
if setting.AutoAssignOrg {
@@ -247,7 +327,7 @@ func getOrCreateOrg(sess *DBSession, orgName string) (int64, error) {
setting.AutoAssignOrgId)
}
org.Name = mainOrgName
org.Name = MainOrgName
org.Id = int64(setting.AutoAssignOrgId)
} else {
org.Name = orgName
+89 -39
View File
@@ -44,17 +44,7 @@ const InitPriority = registry.High
func init() {
ss := &SQLStore{}
// This change will make xorm use an empty default schema for postgres and
// by that mimic the functionality of how it was functioning before
// xorm's changes above.
xorm.DefaultPostgresSchema = ""
registry.Register(&registry.Descriptor{
Name: ServiceName,
Instance: ss,
InitPriority: InitPriority,
})
ss.Register()
}
type SQLStore struct {
@@ -69,23 +59,35 @@ type SQLStore struct {
skipEnsureDefaultOrgAndUser bool
}
// Register registers the SQLStore service with the DI system.
func (ss *SQLStore) Register() {
// This change will make xorm use an empty default schema for postgres and
// by that mimic the functionality of how it was functioning before
// xorm's changes above.
xorm.DefaultPostgresSchema = ""
registry.Register(&registry.Descriptor{
Name: ServiceName,
Instance: ss,
InitPriority: InitPriority,
})
}
func (ss *SQLStore) Init() error {
ss.log = log.New("sqlstore")
ss.readConfig()
engine, err := ss.getEngine()
if err != nil {
if err := ss.initEngine(); err != nil {
return errutil.Wrap("failed to connect to database", err)
}
ss.engine = engine
ss.Dialect = migrator.NewDialect(ss.engine)
// temporarily still set global var
x = engine
x = ss.engine
dialect = ss.Dialect
migrator := migrator.NewMigrator(engine)
migrator := migrator.NewMigrator(ss.engine)
migrations.AddMigrations(migrator)
for _, descriptor := range registry.GetServices() {
@@ -96,7 +98,7 @@ func (ss *SQLStore) Init() error {
}
if err := migrator.Start(); err != nil {
return errutil.Wrap("migration failed", err)
return err
}
// Init repo instances
@@ -109,6 +111,25 @@ func (ss *SQLStore) Init() error {
ss.addAlertNotificationUidByIdHandler()
ss.addPreferencesQueryAndCommandHandlers()
if err := ss.Reset(); err != nil {
return err
}
// Make sure the changes are synced, so they get shared with eventual other DB connections
if err := ss.Sync(); err != nil {
return err
}
return nil
}
// Sync syncs changes to the database.
func (ss *SQLStore) Sync() error {
return ss.engine.Sync2()
}
// Reset resets database state.
// If default org and user creation is enabled, it will be ensured they exist in the database.
func (ss *SQLStore) Reset() error {
if ss.skipEnsureDefaultOrgAndUser {
return nil
}
@@ -141,25 +162,27 @@ func (ss *SQLStore) ensureMainOrgAndAdminUser() error {
// ensure admin user
if !ss.Cfg.DisableInitAdminCreation {
ss.log.Debug("Creating default admin user")
cmd := models.CreateUserCommand{
ss.log.Debug("Creating default admin user")
if _, err := ss.createUser(ctx, userCreationArgs{
Login: ss.Cfg.AdminUser,
Email: ss.Cfg.AdminUser + "@localhost",
Password: ss.Cfg.AdminPassword,
IsAdmin: true,
}
if err := bus.DispatchCtx(ctx, &cmd); err != nil {
}, false); err != nil {
return fmt.Errorf("failed to create admin user: %s", err)
}
ss.log.Info("Created default admin", "user", ss.Cfg.AdminUser)
return nil
// Why should we return and not create the default org in this case?
// Returning here breaks tests using anonymous access
// return nil
}
// ensure default org even if default admin user is disabled
if err := inTransactionCtx(ctx, func(sess *DBSession) error {
_, err := getOrCreateOrg(sess, mainOrgName)
if err := inTransactionWithRetryCtx(ctx, ss.engine, func(sess *DBSession) error {
ss.log.Debug("Creating default org", "name", MainOrgName)
_, err := ss.getOrCreateOrg(sess, MainOrgName)
return err
}); err != nil {
}, 0); err != nil {
return fmt.Errorf("failed to create default organization: %w", err)
}
@@ -253,10 +276,16 @@ func (ss *SQLStore) buildConnectionString() (string, error) {
return cnnstr, nil
}
func (ss *SQLStore) getEngine() (*xorm.Engine, error) {
// initEngine initializes ss.engine.
func (ss *SQLStore) initEngine() error {
if ss.engine != nil {
sqlog.Debug("Already connected to database")
return nil
}
connectionString, err := ss.buildConnectionString()
if err != nil {
return nil, err
return err
}
if ss.Cfg.IsDatabaseMetricsEnabled() {
@@ -264,10 +293,11 @@ func (ss *SQLStore) getEngine() (*xorm.Engine, error) {
}
sqlog.Info("Connecting to DB", "dbtype", ss.dbCfg.Type)
if ss.dbCfg.Type == migrator.SQLite && strings.HasPrefix(connectionString, "file:") {
if ss.dbCfg.Type == migrator.SQLite && strings.HasPrefix(connectionString, "file:") &&
!strings.HasPrefix(connectionString, "file::memory:") {
exists, err := fs.Exists(ss.dbCfg.Path)
if err != nil {
return nil, errutil.Wrapf(err, "can't check for existence of %q", ss.dbCfg.Path)
return errutil.Wrapf(err, "can't check for existence of %q", ss.dbCfg.Path)
}
const perms = 0640
@@ -275,15 +305,15 @@ func (ss *SQLStore) getEngine() (*xorm.Engine, error) {
ss.log.Info("Creating SQLite database file", "path", ss.dbCfg.Path)
f, err := os.OpenFile(ss.dbCfg.Path, os.O_CREATE|os.O_RDWR, perms)
if err != nil {
return nil, errutil.Wrapf(err, "failed to create SQLite database file %q", ss.dbCfg.Path)
return errutil.Wrapf(err, "failed to create SQLite database file %q", ss.dbCfg.Path)
}
if err := f.Close(); err != nil {
return nil, errutil.Wrapf(err, "failed to create SQLite database file %q", ss.dbCfg.Path)
return errutil.Wrapf(err, "failed to create SQLite database file %q", ss.dbCfg.Path)
}
} else {
fi, err := os.Lstat(ss.dbCfg.Path)
if err != nil {
return nil, errutil.Wrapf(err, "failed to stat SQLite database file %q", ss.dbCfg.Path)
return errutil.Wrapf(err, "failed to stat SQLite database file %q", ss.dbCfg.Path)
}
m := fi.Mode() & os.ModePerm
if m|perms != perms {
@@ -294,7 +324,7 @@ func (ss *SQLStore) getEngine() (*xorm.Engine, error) {
}
engine, err := xorm.NewEngine(ss.dbCfg.Type, connectionString)
if err != nil {
return nil, err
return err
}
engine.SetMaxOpenConns(ss.dbCfg.MaxOpenConn)
@@ -311,9 +341,11 @@ func (ss *SQLStore) getEngine() (*xorm.Engine, error) {
engine.ShowExecTime(true)
}
return engine, nil
ss.engine = engine
return nil
}
// readConfig initializes the SQLStore from its configuration.
func (ss *SQLStore) readConfig() {
sec := ss.Cfg.Raw.Section("database")
@@ -363,18 +395,29 @@ type ITestDB interface {
Helper()
Fatalf(format string, args ...interface{})
Logf(format string, args ...interface{})
Log(args ...interface{})
}
var testSQLStore *SQLStore
// InitTestDBOpt contains options for InitTestDB.
type InitTestDBOpt struct {
// EnsureDefaultOrgAndUser flags whether to ensure that default org and user exist.
EnsureDefaultOrgAndUser bool
}
// InitTestDB initializes the test DB.
func InitTestDB(t ITestDB) *SQLStore {
func InitTestDB(t ITestDB, opts ...InitTestDBOpt) *SQLStore {
t.Helper()
if testSQLStore == nil {
testSQLStore = &SQLStore{}
testSQLStore.Bus = bus.New()
testSQLStore.CacheService = localcache.New(5*time.Minute, 10*time.Minute)
testSQLStore.skipEnsureDefaultOrgAndUser = false
testSQLStore.skipEnsureDefaultOrgAndUser = true
for _, opt := range opts {
testSQLStore.skipEnsureDefaultOrgAndUser = !opt.EnsureDefaultOrgAndUser
}
dbType := migrator.SQLite
@@ -423,19 +466,26 @@ func InitTestDB(t ITestDB) *SQLStore {
t.Logf("Cleaning DB")
if err := dialect.CleanDB(); err != nil {
t.Fatalf("Failed to clean test db %v", err)
t.Fatalf("Failed to clean test db: %s", err)
}
if err := testSQLStore.Init(); err != nil {
t.Fatalf("Failed to init test database: %v", err)
t.Fatalf("Failed to init test database: %s", err)
}
t.Log("Successfully initialized test database")
testSQLStore.engine.DatabaseTZ = time.UTC
testSQLStore.engine.TZLocation = time.UTC
return testSQLStore
}
t.Log("Truncating DB tables")
if err := dialect.TruncateDBTables(); err != nil {
t.Fatalf("Failed to truncate test db %v", err)
t.Fatalf("Failed to truncate test db: %s", err)
}
if err := testSQLStore.Reset(); err != nil {
t.Fatalf("Failed to reset SQLStore: %s", err)
}
return testSQLStore
+2 -1
View File
@@ -14,7 +14,8 @@ func SQLite3TestDB() TestDB {
// To run all tests in a local test database, set ConnStr to "grafana_test.db"
return TestDB{
DriverName: "sqlite3",
ConnStr: ":memory:",
// ConnStr specifies an In-memory database shared between connections.
ConnStr: "file::memory:?cache=shared",
}
}
@@ -29,7 +29,7 @@ func TestIntegration_GetUserStats(t *testing.T) {
Email: "admin@test.com",
Name: "Admin",
Login: "admin",
OrgName: mainOrgName,
OrgName: MainOrgName,
IsAdmin: true,
}
err := CreateUser(context.Background(), cmd)
+134
View File
@@ -57,6 +57,140 @@ func getOrgIdForNewUser(sess *DBSession, cmd *models.CreateUserCommand) (int64,
return getOrCreateOrg(sess, orgName)
}
type userCreationArgs struct {
Login string
Email string
Name string
Company string
Password string
IsAdmin bool
IsDisabled bool
EmailVerified bool
OrgID int64
OrgName string
DefaultOrgRole string
}
func (ss *SQLStore) getOrgIDForNewUser(sess *DBSession, args userCreationArgs) (int64, error) {
if ss.Cfg.AutoAssignOrg && args.OrgID != 0 {
if err := verifyExistingOrg(sess, args.OrgID); err != nil {
return -1, err
}
return args.OrgID, nil
}
orgName := args.OrgName
if orgName == "" {
orgName = util.StringsFallback2(args.Email, args.Login)
}
return ss.getOrCreateOrg(sess, orgName)
}
// createUser creates a user in the database.
func (ss *SQLStore) createUser(ctx context.Context, args userCreationArgs, skipOrgSetup bool) (models.User, error) {
var user models.User
if err := inTransactionWithRetryCtx(ctx, ss.engine, func(sess *DBSession) error {
var orgID int64 = -1
if !skipOrgSetup {
var err error
orgID, err = ss.getOrgIDForNewUser(sess, args)
if err != nil {
return err
}
}
if args.Email == "" {
args.Email = args.Login
}
exists, err := sess.Where("email=? OR login=?", args.Email, args.Login).Get(&models.User{})
if err != nil {
return err
}
if exists {
return models.ErrUserAlreadyExists
}
// create user
user = models.User{
Email: args.Email,
Name: args.Name,
Login: args.Login,
Company: args.Company,
IsAdmin: args.IsAdmin,
IsDisabled: args.IsDisabled,
OrgId: orgID,
EmailVerified: args.EmailVerified,
Created: time.Now(),
Updated: time.Now(),
LastSeenAt: time.Now().AddDate(-10, 0, 0),
}
salt, err := util.GetRandomString(10)
if err != nil {
return err
}
user.Salt = salt
rands, err := util.GetRandomString(10)
if err != nil {
return err
}
user.Rands = rands
if len(args.Password) > 0 {
encodedPassword, err := util.EncodePassword(args.Password, user.Salt)
if err != nil {
return err
}
user.Password = encodedPassword
}
sess.UseBool("is_admin")
if _, err := sess.Insert(&user); err != nil {
return err
}
sess.publishAfterCommit(&events.UserCreated{
Timestamp: user.Created,
Id: user.Id,
Name: user.Name,
Login: user.Login,
Email: user.Email,
})
// create org user link
if !skipOrgSetup {
orgUser := models.OrgUser{
OrgId: orgID,
UserId: user.Id,
Role: models.ROLE_ADMIN,
Created: time.Now(),
Updated: time.Now(),
}
if ss.Cfg.AutoAssignOrg && !user.IsAdmin {
if len(args.DefaultOrgRole) > 0 {
orgUser.Role = models.RoleType(args.DefaultOrgRole)
} else {
orgUser.Role = models.RoleType(ss.Cfg.AutoAssignOrgRole)
}
}
if _, err = sess.Insert(&orgUser); err != nil {
return err
}
}
return nil
}, 0); err != nil {
return user, err
}
return user, nil
}
func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
return inTransactionCtx(ctx, func(sess *DBSession) error {
orgId, err := getOrgIdForNewUser(sess, cmd)