SQLStore: Ensure that sessions are always closed (#55864)

* SQLStore: Ensure that sessions are always closed

Delete `NewSession()` in favour of `WithDbSession()`

* Add WithDbSessionForceNewSession to the interface

* Apply suggestions from code review
This commit is contained in:
Sofia Papagiannaki
2022-09-29 15:55:47 +03:00
committed by GitHub
parent c9e957a44e
commit 8b77ee2734
19 changed files with 289 additions and 210 deletions
@@ -17,10 +17,15 @@ import (
func TestPasswordMigrationCommand(t *testing.T) { func TestPasswordMigrationCommand(t *testing.T) {
// setup datasources with password, basic_auth and none // setup datasources with password, basic_auth and none
sqlstore := sqlstore.InitTestDB(t) store := sqlstore.InitTestDB(t)
session := sqlstore.NewSession(context.Background()) err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
defer session.Close() passwordMigration(t, sess, store)
return nil
})
require.NoError(t, err)
}
func passwordMigration(t *testing.T, session *sqlstore.DBSession, sqlstore *sqlstore.SQLStore) {
ds := []*datasources.DataSource{ ds := []*datasources.DataSource{
{Type: "influxdb", Name: "influxdb", Password: "foobar", Uid: "influx"}, {Type: "influxdb", Name: "influxdb", Password: "foobar", Uid: "influx"},
{Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar", Uid: "graphite"}, {Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar", Uid: "graphite"},
+52 -51
View File
@@ -54,36 +54,38 @@ func (dc *databaseCache) internalRunGC() {
func (dc *databaseCache) Get(ctx context.Context, key string) (interface{}, error) { func (dc *databaseCache) Get(ctx context.Context, key string) (interface{}, error) {
cacheHit := CacheData{} cacheHit := CacheData{}
session := dc.SQLStore.NewSession(ctx)
defer session.Close()
exist, err := session.Where("cache_key= ?", key).Get(&cacheHit)
if err != nil {
return nil, err
}
if !exist {
return nil, ErrCacheItemNotFound
}
if cacheHit.Expires > 0 {
existedButExpired := getTime().Unix()-cacheHit.CreatedAt >= cacheHit.Expires
if existedButExpired {
err = dc.Delete(ctx, key) // ignore this error since we will return `ErrCacheItemNotFound` anyway
if err != nil {
dc.log.Debug("Deletion of expired key failed: %v", err)
}
return nil, ErrCacheItemNotFound
}
}
item := &cachedItem{} item := &cachedItem{}
if err = decodeGob(cacheHit.Data, item); err != nil { err := dc.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error {
return nil, err exist, err := session.Where("cache_key= ?", key).Get(&cacheHit)
}
return item.Val, nil if err != nil {
return err
}
if !exist {
return ErrCacheItemNotFound
}
if cacheHit.Expires > 0 {
existedButExpired := getTime().Unix()-cacheHit.CreatedAt >= cacheHit.Expires
if existedButExpired {
err = dc.Delete(ctx, key) // ignore this error since we will return `ErrCacheItemNotFound` anyway
if err != nil {
dc.log.Debug("Deletion of expired key failed: %v", err)
}
return ErrCacheItemNotFound
}
}
if err = decodeGob(cacheHit.Data, item); err != nil {
return err
}
return nil
})
return item.Val, err
} }
func (dc *databaseCache) Set(ctx context.Context, key string, value interface{}, expire time.Duration) error { func (dc *databaseCache) Set(ctx context.Context, key string, value interface{}, expire time.Duration) error {
@@ -93,34 +95,33 @@ func (dc *databaseCache) Set(ctx context.Context, key string, value interface{},
return err return err
} }
session := dc.SQLStore.NewSession(context.Background()) return dc.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error {
defer session.Close() var expiresInSeconds int64
if expire != 0 {
expiresInSeconds = int64(expire) / int64(time.Second)
}
var expiresInSeconds int64 // attempt to insert the key
if expire != 0 { sql := `INSERT INTO cache_data (cache_key,data,created_at,expires) VALUES(?,?,?,?)`
expiresInSeconds = int64(expire) / int64(time.Second) _, err := session.Exec(sql, key, data, getTime().Unix(), expiresInSeconds)
} if err != nil {
// attempt to update if a unique constrain violation or a deadlock (for MySQL) occurs
// attempt to insert the key // if the update fails propagate the error
sql := `INSERT INTO cache_data (cache_key,data,created_at,expires) VALUES(?,?,?,?)` // which eventually will result in a key that is not finally set
_, err = session.Exec(sql, key, data, getTime().Unix(), expiresInSeconds) // but since it's a cache does not harm a lot
if err != nil { if dc.SQLStore.Dialect.IsUniqueConstraintViolation(err) || dc.SQLStore.Dialect.IsDeadlock(err) {
// attempt to update if a unique constrain violation or a deadlock (for MySQL) occurs sql := `UPDATE cache_data SET data=?, created_at=?, expires=? WHERE cache_key=?`
// if the update fails propagate the error _, err = session.Exec(sql, data, getTime().Unix(), expiresInSeconds, key)
// which eventually will result in a key that is not finally set if err != nil && dc.SQLStore.Dialect.IsDeadlock(err) {
// but since it's a cache does not harm a lot // most probably somebody else is upserting the key
if dc.SQLStore.Dialect.IsUniqueConstraintViolation(err) || dc.SQLStore.Dialect.IsDeadlock(err) { // so it is safe enough not to propagate this error
sql := `UPDATE cache_data SET data=?, created_at=?, expires=? WHERE cache_key=?` return nil
_, err = session.Exec(sql, data, getTime().Unix(), expiresInSeconds, key) }
if err != nil && dc.SQLStore.Dialect.IsDeadlock(err) {
// most probably somebody else is upserting the key
// so it is safe enough not to propagate this error
return nil
} }
} }
}
return err return err
})
} }
func (dc *databaseCache) Delete(ctx context.Context, key string) error { func (dc *databaseCache) Delete(ctx context.Context, key string) error {
@@ -43,10 +43,10 @@ func benchmarkFilter(b *testing.B, numDs, numPermissions int) {
require.NoError(b, err) require.NoError(b, err)
var datasources []datasources.DataSource var datasources []datasources.DataSource
sess := store.NewSession(context.Background()) err = store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
err = sess.SQL(baseSql+acFilter.Where, acFilter.Args...).Find(&datasources) return sess.SQL(baseSql+acFilter.Where, acFilter.Args...).Find(&datasources)
})
require.NoError(b, err) require.NoError(b, err)
sess.Close()
require.Len(b, datasources, numPermissions) require.Len(b, datasources, numPermissions)
} }
} }
+34 -33
View File
@@ -168,40 +168,41 @@ func TestFilter_Datasources(t *testing.T) {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
store := sqlstore.InitTestDB(t) store := sqlstore.InitTestDB(t)
sess := store.NewSession(context.Background()) err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
defer sess.Close() // seed 10 data sources
for i := 1; i <= 10; i++ {
// seed 10 data sources dsStore := dsService.CreateStore(store, log.New("accesscontrol.test"))
for i := 1; i <= 10; i++ { err := dsStore.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{Name: fmt.Sprintf("ds:%d", i), Uid: fmt.Sprintf("uid%d", i)})
dsStore := dsService.CreateStore(store, log.New("accesscontrol.test")) require.NoError(t, err)
err := dsStore.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{Name: fmt.Sprintf("ds:%d", i), Uid: fmt.Sprintf("uid%d", i)})
require.NoError(t, err)
}
baseSql := `SELECT data_source.* FROM data_source WHERE`
acFilter, err := accesscontrol.Filter(
&user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{1: tt.permissions},
},
tt.sqlID,
tt.prefix,
tt.actions...,
)
if !tt.expectErr {
require.NoError(t, err)
var datasources []datasources.DataSource
err = sess.SQL(baseSql+acFilter.Where, acFilter.Args...).Find(&datasources)
require.NoError(t, err)
assert.Len(t, datasources, len(tt.expectedDataSources))
for i, ds := range datasources {
assert.Equal(t, tt.expectedDataSources[i], ds.Name)
} }
} else {
require.Error(t, err) baseSql := `SELECT data_source.* FROM data_source WHERE`
} acFilter, err := accesscontrol.Filter(
&user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{1: tt.permissions},
},
tt.sqlID,
tt.prefix,
tt.actions...,
)
if !tt.expectErr {
require.NoError(t, err)
var datasources []datasources.DataSource
err = sess.SQL(baseSql+acFilter.Where, acFilter.Args...).Find(&datasources)
require.NoError(t, err)
assert.Len(t, datasources, len(tt.expectedDataSources))
for i, ds := range datasources {
assert.Equal(t, tt.expectedDataSources[i], ds.Name)
}
} else {
require.Error(t, err)
}
return nil
})
require.NoError(t, err)
}) })
} }
} }
+15 -6
View File
@@ -58,7 +58,9 @@ func (ss *sqlStore) DeleteAlertNotification(ctx context.Context, cmd *models.Del
func (ss *sqlStore) DeleteAlertNotificationWithUid(ctx context.Context, cmd *models.DeleteAlertNotificationWithUidCommand) error { func (ss *sqlStore) DeleteAlertNotificationWithUid(ctx context.Context, cmd *models.DeleteAlertNotificationWithUidCommand) error {
existingNotification := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid} existingNotification := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid}
if err := getAlertNotificationWithUidInternal(ctx, existingNotification, ss.db.NewSession(ctx)); err != nil { if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return getAlertNotificationWithUidInternal(ctx, existingNotification, sess)
}); err != nil {
return err return err
} }
@@ -79,7 +81,9 @@ func (ss *sqlStore) DeleteAlertNotificationWithUid(ctx context.Context, cmd *mod
} }
func (ss *sqlStore) GetAlertNotifications(ctx context.Context, query *models.GetAlertNotificationsQuery) error { func (ss *sqlStore) GetAlertNotifications(ctx context.Context, query *models.GetAlertNotificationsQuery) error {
return getAlertNotificationInternal(ctx, query, ss.db.NewSession(ctx)) return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return getAlertNotificationInternal(ctx, query, sess)
})
} }
func (ss *sqlStore) GetAlertNotificationUidWithId(ctx context.Context, query *models.GetAlertNotificationUidQuery) error { func (ss *sqlStore) GetAlertNotificationUidWithId(ctx context.Context, query *models.GetAlertNotificationUidQuery) error {
@@ -90,8 +94,9 @@ func (ss *sqlStore) GetAlertNotificationUidWithId(ctx context.Context, query *mo
return nil return nil
} }
err := getAlertNotificationUidInternal(ctx, query, ss.db.NewSession(ctx)) if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
if err != nil { return getAlertNotificationUidInternal(ctx, query, sess)
}); err != nil {
return err return err
} }
@@ -105,7 +110,9 @@ func newAlertNotificationUidCacheKey(orgID, notificationId int64) string {
} }
func (ss *sqlStore) GetAlertNotificationsWithUid(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery) error { func (ss *sqlStore) GetAlertNotificationsWithUid(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery) error {
return getAlertNotificationWithUidInternal(ctx, query, ss.db.NewSession(ctx)) return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return getAlertNotificationWithUidInternal(ctx, query, sess)
})
} }
func (ss *sqlStore) GetAllAlertNotifications(ctx context.Context, query *models.GetAllAlertNotificationsQuery) error { func (ss *sqlStore) GetAllAlertNotifications(ctx context.Context, query *models.GetAllAlertNotificationsQuery) error {
@@ -444,7 +451,9 @@ func (ss *sqlStore) UpdateAlertNotification(ctx context.Context, cmd *models.Upd
func (ss *sqlStore) UpdateAlertNotificationWithUid(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error { func (ss *sqlStore) UpdateAlertNotificationWithUid(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error {
getAlertNotificationWithUidQuery := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid} getAlertNotificationWithUidQuery := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid}
if err := getAlertNotificationWithUidInternal(ctx, getAlertNotificationWithUidQuery, ss.db.NewSession(ctx)); err != nil { if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return getAlertNotificationWithUidInternal(ctx, getAlertNotificationWithUidQuery, sess)
}); err != nil {
return err return err
} }
@@ -132,57 +132,62 @@ func TestOldAnnotationsAreDeletedFirst(t *testing.T) {
Created: time.Now().AddDate(-10, 0, -10).UnixNano() / int64(time.Millisecond), Created: time.Now().AddDate(-10, 0, -10).UnixNano() / int64(time.Millisecond),
} }
session := fakeSQL.NewSession(context.Background()) err := fakeSQL.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
defer session.Close() _, err := sess.Insert(a)
require.NoError(t, err, "cannot insert annotation")
_, err = sess.Insert(a)
require.NoError(t, err, "cannot insert annotation")
_, err := session.Insert(a) a.AlertId = 20
require.NoError(t, err, "cannot insert annotation") _, err = sess.Insert(a)
_, err = session.Insert(a) require.NoError(t, err, "cannot insert annotation")
require.NoError(t, err, "cannot insert annotation")
a.AlertId = 20 // run the clean up task to keep one annotation.
_, err = session.Insert(a) cfg := setting.NewCfg()
require.NoError(t, err, "cannot insert annotation") cfg.AnnotationCleanupJobBatchSize = 1
cleaner := &xormRepositoryImpl{cfg: cfg, log: log.New("test-logger"), db: fakeSQL}
_, err = cleaner.CleanAnnotations(context.Background(), setting.AnnotationCleanupSettings{MaxCount: 1}, alertAnnotationType)
require.NoError(t, err)
// run the clean up task to keep one annotation. // assert that the last annotations were kept
cfg := setting.NewCfg() countNew, err := sess.Where("alert_id = 20").Count(&annotations.Item{})
cfg.AnnotationCleanupJobBatchSize = 1 require.NoError(t, err)
cleaner := &xormRepositoryImpl{cfg: cfg, log: log.New("test-logger"), db: fakeSQL} require.Equal(t, int64(1), countNew, "the last annotations should be kept")
_, err = cleaner.CleanAnnotations(context.Background(), setting.AnnotationCleanupSettings{MaxCount: 1}, alertAnnotationType)
countOld, err := sess.Where("alert_id = 10").Count(&annotations.Item{})
require.NoError(t, err)
require.Equal(t, int64(0), countOld, "the two first annotations should have been deleted")
return nil
})
require.NoError(t, err) require.NoError(t, err)
// assert that the last annotations were kept
countNew, err := session.Where("alert_id = 20").Count(&annotations.Item{})
require.NoError(t, err)
require.Equal(t, int64(1), countNew, "the last annotations should be kept")
countOld, err := session.Where("alert_id = 10").Count(&annotations.Item{})
require.NoError(t, err)
require.Equal(t, int64(0), countOld, "the two first annotations should have been deleted")
} }
func assertAnnotationCount(t *testing.T, fakeSQL *sqlstore.SQLStore, sql string, expectedCount int64) { func assertAnnotationCount(t *testing.T, fakeSQL *sqlstore.SQLStore, sql string, expectedCount int64) {
t.Helper() t.Helper()
session := fakeSQL.NewSession(context.Background()) err := fakeSQL.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
defer session.Close() count, err := sess.Where(sql).Count(&annotations.Item{})
count, err := session.Where(sql).Count(&annotations.Item{}) require.NoError(t, err)
require.Equal(t, expectedCount, count)
return nil
})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedCount, count)
} }
func assertAnnotationTagCount(t *testing.T, fakeSQL *sqlstore.SQLStore, expectedCount int64) { func assertAnnotationTagCount(t *testing.T, fakeSQL *sqlstore.SQLStore, expectedCount int64) {
t.Helper() t.Helper()
session := fakeSQL.NewSession(context.Background()) err := fakeSQL.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
defer session.Close() count, err := sess.SQL("select count(*) from annotation_tag").Count()
require.NoError(t, err)
count, err := session.SQL("select count(*) from annotation_tag").Count() require.Equal(t, expectedCount, count)
return nil
})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedCount, count)
} }
func createTestAnnotations(t *testing.T, sqlstore *sqlstore.SQLStore, expectedCount int, oldAnnotations int) { func createTestAnnotations(t *testing.T, store *sqlstore.SQLStore, expectedCount int, oldAnnotations int) {
t.Helper() t.Helper()
cutoffDate := time.Now() cutoffDate := time.Now()
@@ -216,16 +221,19 @@ func createTestAnnotations(t *testing.T, sqlstore *sqlstore.SQLStore, expectedCo
a.Created = cutoffDate.AddDate(-10, 0, -10).UnixNano() / int64(time.Millisecond) a.Created = cutoffDate.AddDate(-10, 0, -10).UnixNano() / int64(time.Millisecond)
} }
_, err := sqlstore.NewSession(context.Background()).Insert(a) err := store.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
require.NoError(t, err, "should be able to save annotation", err) _, err := sess.Insert(a)
require.NoError(t, err, "should be able to save annotation", err)
// mimick the SQL annotation Save logic by writing records to the annotation_tag table // mimick the SQL annotation Save logic by writing records to the annotation_tag table
// we need to ensure they get deleted when we clean up annotations // we need to ensure they get deleted when we clean up annotations
sess := sqlstore.NewSession(context.Background()) for tagID := range []int{1, 2} {
for tagID := range []int{1, 2} { _, err = sess.Exec("INSERT INTO annotation_tag (annotation_id, tag_id) VALUES(?,?)", a.Id, tagID)
_, err = sess.Exec("INSERT INTO annotation_tag (annotation_id, tag_id) VALUES(?,?)", a.Id, tagID) require.NoError(t, err, "should be able to save annotation tag ID", err)
require.NoError(t, err, "should be able to save annotation tag ID", err) }
} return err
})
require.NoError(t, err)
} }
} }
+41 -27
View File
@@ -566,40 +566,54 @@ type testContext struct {
} }
func (c *testContext) getAuthTokenByID(id int64) (*userAuthToken, error) { func (c *testContext) getAuthTokenByID(id int64) (*userAuthToken, error) {
sess := c.sqlstore.NewSession(context.Background()) var res *userAuthToken
var t userAuthToken err := c.sqlstore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
found, err := sess.ID(id).Get(&t) var t userAuthToken
if err != nil || !found { found, err := sess.ID(id).Get(&t)
return nil, err if err != nil || !found {
} return err
}
return &t, nil res = &t
return nil
})
return res, err
} }
func (c *testContext) markAuthTokenAsSeen(id int64) (bool, error) { func (c *testContext) markAuthTokenAsSeen(id int64) (bool, error) {
sess := c.sqlstore.NewSession(context.Background()) hasRowsAffected := false
res, err := sess.Exec("UPDATE user_auth_token SET auth_token_seen = ? WHERE id = ?", c.sqlstore.Dialect.BooleanStr(true), id) err := c.sqlstore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
if err != nil { res, err := sess.Exec("UPDATE user_auth_token SET auth_token_seen = ? WHERE id = ?", c.sqlstore.Dialect.BooleanStr(true), id)
return false, err if err != nil {
} return err
}
rowsAffected, err := res.RowsAffected() rowsAffected, err := res.RowsAffected()
if err != nil { if err != nil {
return false, err return err
} }
return rowsAffected == 1, nil hasRowsAffected = rowsAffected == 1
return nil
})
return hasRowsAffected, err
} }
func (c *testContext) updateRotatedAt(id, rotatedAt int64) (bool, error) { func (c *testContext) updateRotatedAt(id, rotatedAt int64) (bool, error) {
sess := c.sqlstore.NewSession(context.Background()) hasRowsAffected := false
res, err := sess.Exec("UPDATE user_auth_token SET rotated_at = ? WHERE id = ?", rotatedAt, id) err := c.sqlstore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
if err != nil { res, err := sess.Exec("UPDATE user_auth_token SET rotated_at = ? WHERE id = ?", rotatedAt, id)
return false, err if err != nil {
} return err
}
rowsAffected, err := res.RowsAffected() rowsAffected, err := res.RowsAffected()
if err != nil { if err != nil {
return false, err return err
} }
return rowsAffected == 1, nil
hasRowsAffected = rowsAffected == 1
return nil
})
return hasRowsAffected, err
} }
+7 -2
View File
@@ -6,6 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -21,8 +22,12 @@ func TestUserAuthTokenCleanup(t *testing.T) {
insertToken := func(ctx *testContext, token string, prev string, createdAt, rotatedAt int64) { insertToken := func(ctx *testContext, token string, prev string, createdAt, rotatedAt int64) {
ut := userAuthToken{AuthToken: token, PrevAuthToken: prev, CreatedAt: createdAt, RotatedAt: rotatedAt, UserAgent: "", ClientIp: ""} ut := userAuthToken{AuthToken: token, PrevAuthToken: prev, CreatedAt: createdAt, RotatedAt: rotatedAt, UserAgent: "", ClientIp: ""}
_, err := ctx.sqlstore.NewSession(context.Background()).Insert(&ut) err := ctx.sqlstore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
require.Nil(t, err) _, err := sess.Insert(&ut)
require.Nil(t, err)
return nil
})
require.NoError(t, err)
} }
now := time.Date(2018, 12, 13, 13, 45, 0, 0, time.UTC) now := time.Date(2018, 12, 13, 13, 45, 0, 0, time.UTC)
+25 -22
View File
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/ngalert/tests" "github.com/grafana/grafana/pkg/services/ngalert/tests"
"github.com/grafana/grafana/pkg/services/sqlstore"
) )
func TestIntegrationSaveAndGetImage(t *testing.T) { func TestIntegrationSaveAndGetImage(t *testing.T) {
@@ -168,30 +169,32 @@ func TestIntegrationDeleteExpiredImages(t *testing.T) {
image2 := models.Image{URL: "https://example.com/example.png"} image2 := models.Image{URL: "https://example.com/example.png"}
require.NoError(t, dbstore.SaveImage(ctx, &image2)) require.NoError(t, dbstore.SaveImage(ctx, &image2))
s := dbstore.SQLStore.NewSession(ctx) err := dbstore.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
t.Cleanup(s.Close) // should return both images
var result1, result2 models.Image
ok, err := sess.Where("token = ?", image1.Token).Get(&result1)
require.NoError(t, err)
assert.True(t, ok)
ok, err = sess.Where("token = ?", image2.Token).Get(&result2)
require.NoError(t, err)
assert.True(t, ok)
// should return both images // should delete expired image
var result1, result2 models.Image image1.ExpiresAt = time.Now().Add(-time.Second)
ok, err := s.Where("token = ?", image1.Token).Get(&result1) require.NoError(t, dbstore.SaveImage(ctx, &image1))
require.NoError(t, err) n, err := dbstore.DeleteExpiredImages(ctx)
assert.True(t, ok) require.NoError(t, err)
ok, err = s.Where("token = ?", image2.Token).Get(&result2) assert.Equal(t, int64(1), n)
require.NoError(t, err)
assert.True(t, ok)
// should delete expired image // should return just the second image
image1.ExpiresAt = time.Now().Add(-time.Second) ok, err = sess.Where("token = ?", image1.Token).Get(&result1)
require.NoError(t, dbstore.SaveImage(ctx, &image1)) require.NoError(t, err)
n, err := dbstore.DeleteExpiredImages(ctx) assert.False(t, ok)
require.NoError(t, err) ok, err = sess.Where("token = ?", image2.Token).Get(&result2)
assert.Equal(t, int64(1), n) require.NoError(t, err)
assert.True(t, ok)
// should return just the second image return nil
ok, err = s.Where("token = ?", image1.Token).Get(&result1) })
require.NoError(t, err) require.NoError(t, err)
assert.False(t, ok)
ok, err = s.Where("token = ?", image2.Token).Get(&result2)
require.NoError(t, err)
assert.True(t, ok)
} }
+3 -1
View File
@@ -126,7 +126,9 @@ func (ss *SecretsStoreImpl) ReEncryptDataKeys(
currProvider secrets.ProviderID, currProvider secrets.ProviderID,
) error { ) error {
keys := make([]*secrets.DataKey, 0) keys := make([]*secrets.DataKey, 0)
if err := ss.sqlStore.NewSession(ctx).Table(dataKeysTable).Find(&keys); err != nil { if err := ss.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.Table(dataKeysTable).Find(&keys)
}); err != nil {
return err return err
} }
+4 -2
View File
@@ -104,8 +104,10 @@ func (m *SecretsMigrator) RollBackSecrets(ctx context.Context) (bool, error) {
return false, nil return false, nil
} }
_, sqlErr := m.sqlStore.NewSession(ctx).Exec("DELETE FROM data_keys") if sqlErr := m.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
if sqlErr != nil { _, err := sess.Exec("DELETE FROM data_keys")
return err
}); sqlErr != nil {
logger.Warn("Error while cleaning up data keys table...", "error", sqlErr) logger.Warn("Error while cleaning up data keys table...", "error", sqlErr)
return false, nil return false, nil
} }
+12 -4
View File
@@ -18,7 +18,9 @@ func (s simpleSecret) reencrypt(ctx context.Context, secretsSrv *manager.Secrets
Secret []byte Secret []byte
} }
if err := sqlStore.NewSession(ctx).Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows); err != nil { if err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows)
}); err != nil {
logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName) logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName)
return false return false
} }
@@ -72,7 +74,9 @@ func (s b64Secret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsSer
Secret string Secret string
} }
if err := sqlStore.NewSession(ctx).Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows); err != nil { if err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows)
}); err != nil {
logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName) logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName)
return false return false
} }
@@ -140,7 +144,9 @@ func (s jsonSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsSe
SecureJsonData map[string][]byte SecureJsonData map[string][]byte
} }
if err := sqlStore.NewSession(ctx).Table(s.tableName).Cols("id", "secure_json_data").Find(&rows); err != nil { if err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.Table(s.tableName).Cols("id", "secure_json_data").Find(&rows)
}); err != nil {
logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName) logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName)
return false return false
} }
@@ -199,7 +205,9 @@ func (s alertingSecret) reencrypt(ctx context.Context, secretsSrv *manager.Secre
} }
selectSQL := "SELECT id, alertmanager_configuration FROM alert_configuration" selectSQL := "SELECT id, alertmanager_configuration FROM alert_configuration"
if err := sqlStore.NewSession(ctx).SQL(selectSQL).Find(&results); err != nil { if err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.SQL(selectSQL).Find(&results)
}); err != nil {
logger.Warn("Could not find any alert_configuration secret to re-encrypt") logger.Warn("Could not find any alert_configuration secret to re-encrypt")
return false return false
} }
+12 -4
View File
@@ -24,7 +24,9 @@ func (s simpleSecret) rollback(
Secret []byte Secret []byte
} }
if err := sqlStore.NewSession(ctx).Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows); err != nil { if err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows)
}); err != nil {
logger.Warn("Could not find any secret to roll back", "table", s.tableName) logger.Warn("Could not find any secret to roll back", "table", s.tableName)
return true return true
} }
@@ -82,7 +84,9 @@ func (s b64Secret) rollback(
Secret string Secret string
} }
if err := sqlStore.NewSession(ctx).Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows); err != nil { if err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows)
}); err != nil {
logger.Warn("Could not find any secret to roll back", "table", s.tableName) logger.Warn("Could not find any secret to roll back", "table", s.tableName)
return true return true
} }
@@ -154,7 +158,9 @@ func (s jsonSecret) rollback(
SecureJsonData map[string][]byte SecureJsonData map[string][]byte
} }
if err := sqlStore.NewSession(ctx).Table(s.tableName).Cols("id", "secure_json_data").Find(&rows); err != nil { if err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.Table(s.tableName).Cols("id", "secure_json_data").Find(&rows)
}); err != nil {
logger.Warn("Could not find any secret to roll back", "table", s.tableName) logger.Warn("Could not find any secret to roll back", "table", s.tableName)
return true return true
} }
@@ -217,7 +223,9 @@ func (s alertingSecret) rollback(
} }
selectSQL := "SELECT id, alertmanager_configuration FROM alert_configuration" selectSQL := "SELECT id, alertmanager_configuration FROM alert_configuration"
if err := sqlStore.NewSession(ctx).SQL(selectSQL).Find(&results); err != nil { if err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.SQL(selectSQL).Find(&results)
}); err != nil {
logger.Warn("Could not find any alert_configuration secret to roll back") logger.Warn("Could not find any alert_configuration secret to roll back")
return true return true
} }
+1 -1
View File
@@ -12,7 +12,7 @@ import (
type DB interface { type DB interface {
WithTransactionalDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error WithTransactionalDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error
WithDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error WithDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error
NewSession(ctx context.Context) *sqlstore.DBSession WithNewDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error
GetDialect() migrator.Dialect GetDialect() migrator.Dialect
GetDBType() core.DbType GetDBType() core.DbType
GetSqlxSession() *session.SessionDB GetSqlxSession() *session.SessionDB
@@ -21,3 +21,7 @@ func (f *FakeDB) WithTransactionalDbSession(ctx context.Context, callback sqlsto
func (f *FakeDB) WithDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error { func (f *FakeDB) WithDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error {
return f.ExpectedError return f.ExpectedError
} }
func (f *FakeDB) WithNewDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error {
return f.ExpectedError
}
@@ -222,6 +222,10 @@ func (m *SQLStoreMock) WithDbSession(ctx context.Context, callback sqlstore.DBTr
return m.ExpectedError return m.ExpectedError
} }
func (m *SQLStoreMock) WithNewDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error {
return m.ExpectedError
}
func (m *SQLStoreMock) GetOrgQuotaByTarget(ctx context.Context, query *models.GetOrgQuotaByTargetQuery) error { func (m *SQLStoreMock) GetOrgQuotaByTarget(ctx context.Context, query *models.GetOrgQuotaByTargetQuery) error {
return m.ExpectedError return m.ExpectedError
} }
+11 -8
View File
@@ -27,13 +27,6 @@ func (sess *DBSession) PublishAfterCommit(msg interface{}) {
sess.events = append(sess.events, msg) sess.events = append(sess.events, msg)
} }
// NewSession returns a new DBSession
func (ss *SQLStore) NewSession(ctx context.Context) *DBSession {
sess := &DBSession{Session: ss.engine.NewSession()}
sess.Session = sess.Session.Context(ctx)
return sess
}
func startSessionOrUseExisting(ctx context.Context, engine *xorm.Engine, beginTran bool) (*DBSession, bool, error) { func startSessionOrUseExisting(ctx context.Context, engine *xorm.Engine, beginTran bool) (*DBSession, bool, error) {
value := ctx.Value(ContextSessionKey{}) value := ctx.Value(ContextSessionKey{})
var sess *DBSession var sess *DBSession
@@ -55,14 +48,24 @@ func startSessionOrUseExisting(ctx context.Context, engine *xorm.Engine, beginTr
} }
newSess.Session = newSess.Session.Context(ctx) newSess.Session = newSess.Session.Context(ctx)
return newSess, true, nil return newSess, true, nil
} }
// WithDbSession calls the callback with a session. // WithDbSession calls the callback with the session in the context (if exists).
// Otherwise it creates a new one that is closed upon completion.
// A session is stored in the context if sqlstore.InTransaction() has been been previously called with the same context (and it's not committed/rolledback yet).
func (ss *SQLStore) WithDbSession(ctx context.Context, callback DBTransactionFunc) error { func (ss *SQLStore) WithDbSession(ctx context.Context, callback DBTransactionFunc) error {
return withDbSession(ctx, ss.engine, callback) return withDbSession(ctx, ss.engine, callback)
} }
// WithNewDbSession calls the callback with a new session that is closed upon completion.
func (ss *SQLStore) WithNewDbSession(ctx context.Context, callback DBTransactionFunc) error {
sess := &DBSession{Session: ss.engine.NewSession(), transactionOpen: false}
defer sess.Close()
return callback(sess)
}
func withDbSession(ctx context.Context, engine *xorm.Engine, callback DBTransactionFunc) error { func withDbSession(ctx context.Context, engine *xorm.Engine, callback DBTransactionFunc) error {
sess, isNew, err := startSessionOrUseExisting(ctx, engine, false) sess, isNew, err := startSessionOrUseExisting(ctx, engine, false)
if err != nil { if err != nil {
+1 -1
View File
@@ -30,8 +30,8 @@ type Store interface {
GetSignedInUser(ctx context.Context, query *models.GetSignedInUserQuery) error GetSignedInUser(ctx context.Context, query *models.GetSignedInUserQuery) error
UpdateUserPermissions(userID int64, isAdmin bool) error UpdateUserPermissions(userID int64, isAdmin bool) error
SetUserHelpFlag(ctx context.Context, cmd *models.SetUserHelpFlagCommand) error SetUserHelpFlag(ctx context.Context, cmd *models.SetUserHelpFlagCommand) error
NewSession(ctx context.Context) *DBSession
WithDbSession(ctx context.Context, callback DBTransactionFunc) error WithDbSession(ctx context.Context, callback DBTransactionFunc) error
WithNewDbSession(ctx context.Context, callback DBTransactionFunc) error
GetOrgQuotaByTarget(ctx context.Context, query *models.GetOrgQuotaByTargetQuery) error GetOrgQuotaByTarget(ctx context.Context, query *models.GetOrgQuotaByTargetQuery) error
GetOrgQuotas(ctx context.Context, query *models.GetOrgQuotasQuery) error GetOrgQuotas(ctx context.Context, query *models.GetOrgQuotasQuery) error
UpdateOrgQuota(ctx context.Context, cmd *models.UpdateOrgQuotaCmd) error UpdateOrgQuota(ctx context.Context, cmd *models.UpdateOrgQuotaCmd) error
+2
View File
@@ -20,6 +20,8 @@ func (ss *SQLStore) WithTransactionalDbSession(ctx context.Context, callback DBT
return inTransactionWithRetryCtx(ctx, ss.engine, ss.bus, callback, 0) return inTransactionWithRetryCtx(ctx, ss.engine, ss.bus, callback, 0)
} }
// InTransaction starts a transaction and calls the fn
// It stores the session in the context
func (ss *SQLStore) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error { func (ss *SQLStore) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
return ss.inTransactionWithRetry(ctx, fn, 0) return ss.inTransactionWithRetry(ctx, fn, 0)
} }