Alerting: Generate deterministic UID for folders created for Prometheus rule conversion (#101760)
This commit is contained in:
@@ -3,7 +3,10 @@ package store
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@@ -89,7 +92,14 @@ func (st DBstore) GetNamespaceByTitle(ctx context.Context, title string, orgID i
|
||||
}
|
||||
|
||||
// GetOrCreateNamespaceByTitle gets or creates a namespace by title in the specified folder.
|
||||
//
|
||||
// To avoid race conditions when two concurrent requests try to create the same folder,
|
||||
// we create folders with a deterministic UID based on the parent UID, title, and organization ID.
|
||||
func (st DBstore) GetOrCreateNamespaceByTitle(ctx context.Context, title string, orgID int64, user identity.Requester, parentUID string) (*folder.Folder, error) {
|
||||
if len(title) == 0 {
|
||||
return nil, fmt.Errorf("title is empty")
|
||||
}
|
||||
|
||||
var f *folder.Folder
|
||||
var err error
|
||||
|
||||
@@ -99,7 +109,14 @@ func (st DBstore) GetOrCreateNamespaceByTitle(ctx context.Context, title string,
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
// Generate a deterministic UID with an alerting prefix
|
||||
uid, err := generateAlertingFolderUID(title, parentUID, orgID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating a new folder: %w", err)
|
||||
}
|
||||
|
||||
cmd := &folder.CreateFolderCommand{
|
||||
UID: uid,
|
||||
OrgID: orgID,
|
||||
Title: title,
|
||||
SignedInUser: user,
|
||||
@@ -107,9 +124,49 @@ func (st DBstore) GetOrCreateNamespaceByTitle(ctx context.Context, title string,
|
||||
}
|
||||
f, err = st.FolderService.Create(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Handle potential race condition where another request might have created
|
||||
// the folder between our check and creation attempt
|
||||
existingFolder, lookupErr := st.GetNamespaceByTitle(ctx, title, orgID, user, parentUID)
|
||||
if lookupErr == nil {
|
||||
return existingFolder, nil
|
||||
}
|
||||
|
||||
// If we couldn't find it, return errors
|
||||
return nil, fmt.Errorf("failed to get or create folder: %w", errors.Join(
|
||||
fmt.Errorf("create folder: %w", err),
|
||||
fmt.Errorf("lookup folder: %w", lookupErr),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// generateAlertingFolderUID creates a deterministic UID for folders
|
||||
// based on the title and parent UID to avoid race conditions when multiple
|
||||
// identical folders are created concurrently
|
||||
func generateAlertingFolderUID(title string, parentUID string, orgID int64) (string, error) {
|
||||
h := fnv.New64a()
|
||||
|
||||
hashData := [][]byte{
|
||||
[]byte(parentUID),
|
||||
{0}, // separator
|
||||
[]byte(title),
|
||||
{0},
|
||||
[]byte(strconv.FormatInt(orgID, 10)),
|
||||
}
|
||||
|
||||
// Add hashData strings to the hash with a separator between them
|
||||
for _, data := range hashData {
|
||||
_, err := h.Write(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Create a deterministic string with alerting prefix
|
||||
base36 := strconv.FormatUint(h.Sum64(), 36)
|
||||
uid := fmt.Sprintf("alerting-%s", base36)
|
||||
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user