Storage: Propagate RV to server on update+delete (#111866)
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"google.golang.org/protobuf/proto"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/sqlite"
|
||||
|
||||
@@ -405,15 +406,18 @@ func (b *backend) update(ctx context.Context, event resource.WriteEvent) (int64,
|
||||
// Use rvManager.ExecWithRV instead of direct transaction
|
||||
rv, err := b.rvManager.ExecWithRV(ctx, event.Key, func(tx db.Tx) (string, error) {
|
||||
// 1. Update resource
|
||||
_, err := dbutil.Exec(ctx, tx, sqlResourceUpdate, sqlResourceRequest{
|
||||
res, err := dbutil.Exec(ctx, tx, sqlResourceUpdate, sqlResourceRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
WriteEvent: event,
|
||||
WriteEvent: event, // includes the RV
|
||||
Folder: folder,
|
||||
GUID: event.GUID,
|
||||
})
|
||||
if err != nil {
|
||||
return event.GUID, fmt.Errorf("resource update: %w", err)
|
||||
}
|
||||
if err = b.checkConflict(res, event.Key, event.PreviousRV); err != nil {
|
||||
return event.GUID, err
|
||||
}
|
||||
|
||||
// 2. Insert into resource history
|
||||
if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{
|
||||
@@ -460,7 +464,7 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64,
|
||||
}
|
||||
rv, err := b.rvManager.ExecWithRV(ctx, event.Key, func(tx db.Tx) (string, error) {
|
||||
// 1. delete from resource
|
||||
_, err := dbutil.Exec(ctx, tx, sqlResourceDelete, sqlResourceRequest{
|
||||
res, err := dbutil.Exec(ctx, tx, sqlResourceDelete, sqlResourceRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
WriteEvent: event,
|
||||
GUID: event.GUID,
|
||||
@@ -468,6 +472,9 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64,
|
||||
if err != nil {
|
||||
return event.GUID, fmt.Errorf("delete resource: %w", err)
|
||||
}
|
||||
if err = b.checkConflict(res, event.Key, event.PreviousRV); err != nil {
|
||||
return event.GUID, err
|
||||
}
|
||||
|
||||
// 2. Add event to resource history
|
||||
if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{
|
||||
@@ -504,6 +511,28 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64,
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (b *backend) checkConflict(res db.Result, key *resourcepb.ResourceKey, rv int64) error {
|
||||
if rv == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The RV is part of the update request, and it may no longer be the most recent
|
||||
rows, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to verify RV: %w", err)
|
||||
}
|
||||
if rows == 1 {
|
||||
return nil // expected one result
|
||||
}
|
||||
if rows > 0 {
|
||||
return fmt.Errorf("multiple rows effected (%d)", rows)
|
||||
}
|
||||
return apierrors.NewConflict(schema.GroupResource{
|
||||
Group: key.Group,
|
||||
Resource: key.Resource,
|
||||
}, key.Name, fmt.Errorf("resource version does not match current value"))
|
||||
}
|
||||
|
||||
func (b *backend) ReadResource(ctx context.Context, req *resourcepb.ReadRequest) *resource.BackendReadResponse {
|
||||
_, span := b.tracer.Start(ctx, tracePrefix+".Read")
|
||||
defer span.End()
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
|
||||
@@ -5,5 +5,6 @@ DELETE FROM {{ .Ident "resource" }}
|
||||
AND {{ .Ident "resource" }} = {{ .Arg .WriteEvent.Key.Resource }}
|
||||
{{ if .WriteEvent.Key.Name }}
|
||||
AND {{ .Ident "name" }} = {{ .Arg .WriteEvent.Key.Name }}
|
||||
AND {{ .Ident "resource_version" }} = {{ .Arg .WriteEvent.PreviousRV }}
|
||||
{{ end }}
|
||||
;
|
||||
|
||||
@@ -10,4 +10,5 @@ UPDATE {{ .Ident "resource" }}
|
||||
AND {{ .Ident "resource" }} = {{ .Arg .WriteEvent.Key.Resource }}
|
||||
AND {{ .Ident "namespace" }} = {{ .Arg .WriteEvent.Key.Namespace }}
|
||||
AND {{ .Ident "name" }} = {{ .Arg .WriteEvent.Key.Name }}
|
||||
AND {{ .Ident "resource_version" }} = {{ .Arg .WriteEvent.PreviousRV }}
|
||||
;
|
||||
|
||||
@@ -87,14 +87,23 @@ func TestIntegrationListIter(t *testing.T) {
|
||||
Group: item.group,
|
||||
Name: item.name,
|
||||
},
|
||||
Value: item.value,
|
||||
PreviousRV: 0,
|
||||
Value: item.value,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert test data: %w", err)
|
||||
}
|
||||
_, err = dbutil.Exec(ctx, tx, sqlResourceUpdate, sqlResourceRequest{
|
||||
|
||||
if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{
|
||||
SQLTemplate: sqltemplate.New(dialect),
|
||||
GUIDToRV: map[string]int64{
|
||||
item.guid: item.resourceVersion,
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to insert test data: %w", err)
|
||||
}
|
||||
|
||||
if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdate, sqlResourceRequest{
|
||||
SQLTemplate: sqltemplate.New(dialect),
|
||||
GUID: item.guid,
|
||||
ResourceVersion: item.resourceVersion,
|
||||
@@ -110,8 +119,7 @@ func TestIntegrationListIter(t *testing.T) {
|
||||
PreviousRV: item.resourceVersion,
|
||||
Type: 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to insert resource version: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,21 @@ func TestUnifiedStorageQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "with rv",
|
||||
Data: &sqlResourceRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
WriteEvent: resource.WriteEvent{
|
||||
Key: &resourcepb.ResourceKey{
|
||||
Namespace: "nn",
|
||||
Group: "gg",
|
||||
Resource: "rr",
|
||||
Name: "name",
|
||||
},
|
||||
PreviousRV: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlResourceInsert: {
|
||||
{
|
||||
@@ -63,6 +78,7 @@ func TestUnifiedStorageQueries(t *testing.T) {
|
||||
Resource: "rr",
|
||||
Name: "name",
|
||||
},
|
||||
PreviousRV: 1759304090100678,
|
||||
},
|
||||
Folder: "fldr",
|
||||
},
|
||||
|
||||
@@ -263,7 +263,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource
|
||||
attribute.Int("operation_index", i),
|
||||
attribute.String("error", err.Error()),
|
||||
))
|
||||
return fmt.Errorf("failed to execute function: %w", err)
|
||||
return err
|
||||
}
|
||||
guids[i] = guid
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ DELETE FROM `resource`
|
||||
AND `group` = 'gg'
|
||||
AND `resource` = 'rr'
|
||||
AND `name` = 'name'
|
||||
AND `resource_version` = 0
|
||||
;
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
DELETE FROM `resource`
|
||||
WHERE 1 = 1
|
||||
AND `namespace` = 'nn'
|
||||
AND `group` = 'gg'
|
||||
AND `resource` = 'rr'
|
||||
AND `name` = 'name'
|
||||
AND `resource_version` = 1234
|
||||
;
|
||||
@@ -10,4 +10,5 @@ UPDATE `resource`
|
||||
AND `resource` = 'rr'
|
||||
AND `namespace` = 'nn'
|
||||
AND `name` = 'name'
|
||||
AND `resource_version` = 1759304090100678
|
||||
;
|
||||
|
||||
@@ -4,4 +4,5 @@ DELETE FROM "resource"
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "name" = 'name'
|
||||
AND "resource_version" = 0
|
||||
;
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
DELETE FROM "resource"
|
||||
WHERE 1 = 1
|
||||
AND "namespace" = 'nn'
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "name" = 'name'
|
||||
AND "resource_version" = 1234
|
||||
;
|
||||
@@ -10,4 +10,5 @@ UPDATE "resource"
|
||||
AND "resource" = 'rr'
|
||||
AND "namespace" = 'nn'
|
||||
AND "name" = 'name'
|
||||
AND "resource_version" = 1759304090100678
|
||||
;
|
||||
|
||||
@@ -4,4 +4,5 @@ DELETE FROM "resource"
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "name" = 'name'
|
||||
AND "resource_version" = 0
|
||||
;
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
DELETE FROM "resource"
|
||||
WHERE 1 = 1
|
||||
AND "namespace" = 'nn'
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "name" = 'name'
|
||||
AND "resource_version" = 1234
|
||||
;
|
||||
@@ -10,4 +10,5 @@ UPDATE "resource"
|
||||
AND "resource" = 'rr'
|
||||
AND "namespace" = 'nn'
|
||||
AND "name" = 'name'
|
||||
AND "resource_version" = 1759304090100678
|
||||
;
|
||||
|
||||
Reference in New Issue
Block a user