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:
Dan Cech
2023-12-06 21:21:21 +01:00
committed by GitHub
parent 07915703fe
commit c4c9bfaf2e
42 changed files with 4358 additions and 2389 deletions
@@ -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
}