Storage: Unified Storage based on Entity API (#71977)
* first round of entityapi updates - quote column names and clean up insert/update queries - replace grn with guid - streamline table structure fixes streamline entity history move EntitySummary into proto remove EntitySummary add guid to json fix tests change DB_Uuid to DB_NVarchar fix folder test convert interface to any more cleanup start entity store under grafana-apiserver dskit target CRUD working, kind of rough cut of wiring entity api to kube-apiserver fake grafana user in context add key to entity list working revert unnecessary changes move entity storage files to their own package, clean up use accessor to read/write grafana annotations implement separate Create and Update functions * go mod tidy * switch from Kind to resource * basic grpc storage server * basic support for grpc entity store * don't connect to database unless it's needed, pass user identity over grpc * support getting user from k8s context, fix some mysql issues * assign owner to snowflake dependency * switch from ulid to uuid for guids * cleanup, rename Search to List * remove entityListResult * EntityAPI: remove extra user abstraction (#79033) * remove extra user abstraction * add test stub (but * move grpc context setup into client wrapper, fix lint issue * remove unused constants * remove custom json stuff * basic list filtering, add todo * change target to storage-server, allow entityStore flag in prod mode * fix issue with Update * EntityAPI: make test work, need to resolve expected differences (#79123) * make test work, need to resolve expected differences * remove the fields not supported by legacy * sanitize out the bits legacy does not support * sanitize out the bits legacy does not support --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * update feature toggle generated files * remove unused http headers * update feature flag strategy * devmode * update readme * spelling * readme --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
@@ -4,18 +4,19 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/grn"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/session"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
)
|
||||
|
||||
type folderInfo struct {
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"` // original display name
|
||||
Slug string `json:"slug"` // full slug
|
||||
Guid string `json:"guid"`
|
||||
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"` // original display name
|
||||
SlugPath string `json:"slug"` // full slug path
|
||||
|
||||
// original slug
|
||||
originalSlug string
|
||||
Slug string `json:"-"`
|
||||
|
||||
depth int32
|
||||
left int32
|
||||
@@ -33,51 +34,48 @@ type folderInfo struct {
|
||||
// This will replace all entries in `entity_folder`
|
||||
// This is pretty heavy weight, but it does give us a sorted folder list
|
||||
// NOTE: this could be done async with a mutex/lock? reconciler pattern
|
||||
func updateFolderTree(ctx context.Context, tx *session.SessionTx, tenant int64) error {
|
||||
_, err := tx.Exec(ctx, "DELETE FROM entity_folder WHERE tenant_id=?", tenant)
|
||||
func updateFolderTree(ctx context.Context, tx *session.SessionTx, tenantId int64) error {
|
||||
_, err := tx.Exec(ctx, "DELETE FROM entity_folder WHERE tenant_id=?", tenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query := "SELECT guid,uid,folder,name,slug" +
|
||||
" FROM entity" +
|
||||
" WHERE kind=? AND tenant_id=?" +
|
||||
" ORDER BY slug asc"
|
||||
args := []interface{}{entity.StandardKindFolder, tenantId}
|
||||
|
||||
all := []*folderInfo{}
|
||||
rows, err := tx.Query(ctx, "SELECT uid,folder,name,slug FROM entity WHERE kind=? AND tenant_id=? ORDER BY slug asc;",
|
||||
entity.StandardKindFolder, tenant)
|
||||
rows, err := tx.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
for rows.Next() {
|
||||
folder := folderInfo{
|
||||
children: []*folderInfo{},
|
||||
}
|
||||
err = rows.Scan(&folder.UID, &folder.parentUID, &folder.Name, &folder.originalSlug)
|
||||
err = rows.Scan(&folder.Guid, &folder.UID, &folder.parentUID, &folder.Name, &folder.Slug)
|
||||
if err != nil {
|
||||
break
|
||||
return err
|
||||
}
|
||||
all = append(all, &folder)
|
||||
}
|
||||
errClose := rows.Close()
|
||||
// TODO: Use some kind of multi-error.
|
||||
// Until then, we want to prioritize errors coming from the .Scan
|
||||
// over those coming from .Close.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errClose != nil {
|
||||
return errClose
|
||||
}
|
||||
|
||||
root, lost, err := buildFolderTree(all)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = insertFolderInfo(ctx, tx, tenant, root, false)
|
||||
err = insertFolderInfo(ctx, tx, tenantId, root, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, folder := range lost {
|
||||
err = insertFolderInfo(ctx, tx, tenant, folder, true)
|
||||
err = insertFolderInfo(ctx, tx, tenantId, folder, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -123,9 +121,9 @@ func setMPTTOrder(folder *folderInfo, stack []*folderInfo, idx int32) (int32, er
|
||||
folder.stack = stack
|
||||
|
||||
if folder.depth > 0 {
|
||||
folder.Slug = "/"
|
||||
folder.SlugPath = "/"
|
||||
for _, f := range stack {
|
||||
folder.Slug += f.originalSlug + "/"
|
||||
folder.SlugPath += f.Slug + "/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,17 +137,16 @@ func setMPTTOrder(folder *folderInfo, stack []*folderInfo, idx int32) (int32, er
|
||||
return folder.right, nil
|
||||
}
|
||||
|
||||
func insertFolderInfo(ctx context.Context, tx *session.SessionTx, tenant int64, folder *folderInfo, isDetached bool) error {
|
||||
func insertFolderInfo(ctx context.Context, tx *session.SessionTx, tenantId int64, folder *folderInfo, isDetached bool) error {
|
||||
js, _ := json.Marshal(folder.stack)
|
||||
grn2 := grn.GRN{TenantID: tenant, ResourceKind: entity.StandardKindFolder, ResourceIdentifier: folder.UID}
|
||||
_, err := tx.Exec(ctx,
|
||||
`INSERT INTO entity_folder `+
|
||||
"(grn, tenant_id, uid, slug_path, tree, depth, left, right, detached) "+
|
||||
"(guid, tenant_id, uid, slug_path, tree, depth, lft, rgt, detached) "+
|
||||
`VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
grn2.ToGRNString(),
|
||||
tenant,
|
||||
folder.Guid,
|
||||
tenantId,
|
||||
folder.UID,
|
||||
folder.Slug,
|
||||
folder.SlugPath,
|
||||
string(js),
|
||||
folder.depth,
|
||||
folder.left,
|
||||
@@ -161,7 +158,7 @@ func insertFolderInfo(ctx context.Context, tx *session.SessionTx, tenant int64,
|
||||
}
|
||||
|
||||
for _, sub := range folder.children {
|
||||
err := insertFolderInfo(ctx, tx, tenant, sub, isDetached)
|
||||
err := insertFolderInfo(ctx, tx, tenantId, sub, isDetached)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user