fix: create dashboard in legacy storage within transaction (#114808)
fix: create dashboard within transaction
This commit is contained in:
@@ -140,11 +140,30 @@ func NewDashboardSQLAccess(sql legacysql.LegacyDatabaseProvider,
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) executeQuery(ctx context.Context, helper *legacysql.LegacyDatabaseHelper, query string, args ...any) (*sql.Rows, error) {
|
||||
// Use transaction if available in context.
|
||||
var tx *sql.Tx
|
||||
// After this function runs, the `tx` variable will only be set if
|
||||
// this function was called in the context of a transaction set up by a
|
||||
// caller upstream. In that case, we reuse the transaction.
|
||||
_ = helper.DB.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
coreTx, err := sess.Tx()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tx = coreTx.Tx
|
||||
return nil
|
||||
})
|
||||
|
||||
// Use transaction from unified storage if available in the context.
|
||||
// This allows us to run migrations in a transaction which is specifically required for SQLite.
|
||||
if tx := resource.TransactionFromContext(ctx); tx != nil {
|
||||
if tx == nil {
|
||||
tx = resource.TransactionFromContext(ctx)
|
||||
}
|
||||
|
||||
if tx != nil {
|
||||
return tx.QueryContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
return helper.DB.GetSqlxSession().Query(ctx, query, args...)
|
||||
}
|
||||
|
||||
|
||||
@@ -132,10 +132,19 @@ func (a *dashboardSqlAccess) WriteEvent(ctx context.Context, event resource.Writ
|
||||
}
|
||||
} else {
|
||||
failOnExisting := event.Type == resourcepb.WatchEvent_ADDED
|
||||
after, _, err := a.SaveDashboard(ctx, info.OrgID, dash, failOnExisting)
|
||||
sql, err := a.sql(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var after *dashboard.Dashboard
|
||||
if err := sql.DB.InTransaction(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
after, _, err = a.SaveDashboard(ctx, info.OrgID, dash, failOnExisting)
|
||||
return err
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if after != nil {
|
||||
meta, err := utils.MetaAccessor(after)
|
||||
if err != nil {
|
||||
|
||||
@@ -233,6 +233,7 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) {
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("When updating uid with a dashboard already using that uid", func(t *testing.T) {
|
||||
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||
"dashboard": map[string]interface{}{
|
||||
@@ -266,6 +267,34 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) {
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("When creating a dashboard that references a non-existent library panel", func(t *testing.T) {
|
||||
originalCount := getDashboardCount(t, grafanaListedAddr, "admin", "admin")
|
||||
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||
"dashboard": map[string]interface{}{
|
||||
"title": "Bad dashboard",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"gridPos": map[string]int{"h": 0, "w": 0, "x": 0, "y": 0},
|
||||
"libraryPanel": map[string]string{
|
||||
"name": "Bad panel",
|
||||
"uid": "invalid-uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), "library element could not be found")
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// A new dashboard is not created in this situation.
|
||||
require.Equal(t, originalCount, getDashboardCount(t, grafanaListedAddr, "admin", "admin"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationDashboardQuota(t *testing.T) {
|
||||
@@ -982,6 +1011,21 @@ func postDashboard(t *testing.T, grafanaListedAddr, user, password string, paylo
|
||||
return http.Post(u, "application/json", bytes.NewBuffer(payloadBytes)) // nolint:gosec
|
||||
}
|
||||
|
||||
func getDashboardCount(t *testing.T, grafanaListenAddr, user, password string) int {
|
||||
endpoint := fmt.Sprintf("http://%s:%s@%s/apis/dashboard.grafana.app/v0alpha1/namespaces/default/search", user, password, grafanaListenAddr)
|
||||
resp, err := http.Get(endpoint) //nolint:gosec
|
||||
require.NoError(t, err)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, resp.Body.Close())
|
||||
|
||||
var payload map[string]any
|
||||
require.NoError(t, json.Unmarshal(body, &payload))
|
||||
|
||||
return int(payload["totalHits"].(float64))
|
||||
}
|
||||
|
||||
func TestIntegrationDashboardServicePermissions(t *testing.T) {
|
||||
testutil.SkipIntegrationTestInShortMode(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user