diff --git a/pkg/services/store/object/dummy/dummy_server.go b/pkg/services/store/object/dummy/dummy_server.go index 6dd318399c9..a5a76078c9e 100644 --- a/pkg/services/store/object/dummy/dummy_server.go +++ b/pkg/services/store/object/dummy/dummy_server.go @@ -101,7 +101,8 @@ func (i *dummyObjectServer) findObject(ctx context.Context, grn *object.GRN, ver } func (i *dummyObjectServer) Read(ctx context.Context, r *object.ReadObjectRequest) (*object.ReadObjectResponse, error) { - _, objVersion, err := i.findObject(ctx, r.GRN, r.Version) + grn := getFullGRN(ctx, r.GRN) + _, objVersion, err := i.findObject(ctx, grn, r.Version) if err != nil { return nil, err } @@ -175,7 +176,7 @@ func (i *dummyObjectServer) update(ctx context.Context, r *object.WriteObjectReq GRN: r.GRN, Created: i.Object.Created, CreatedBy: i.Object.CreatedBy, - Updated: time.Now().Unix(), + Updated: time.Now().UnixMilli(), UpdatedBy: store.GetUserIDString(modifier), Size: int64(len(r.Body)), ETag: createContentsHash(r.Body), @@ -225,8 +226,8 @@ func (i *dummyObjectServer) insert(ctx context.Context, r *object.WriteObjectReq modifier := store.GetUserIDString(store.UserFromContext(ctx)) rawObj := &object.RawObject{ GRN: r.GRN, - Updated: time.Now().Unix(), - Created: time.Now().Unix(), + Updated: time.Now().UnixMilli(), + Created: time.Now().UnixMilli(), CreatedBy: modifier, UpdatedBy: modifier, Size: int64(len(r.Body)), @@ -265,12 +266,13 @@ func (i *dummyObjectServer) insert(ctx context.Context, r *object.WriteObjectReq } func (i *dummyObjectServer) Write(ctx context.Context, r *object.WriteObjectRequest) (*object.WriteObjectResponse, error) { - namespace := namespaceFromUID(r.GRN) + grn := getFullGRN(ctx, r.GRN) + namespace := namespaceFromUID(grn) obj, err := i.collection.FindFirst(ctx, namespace, func(i *RawObjectWithHistory) (bool, error) { if i == nil || r == nil { return false, nil } - return r.GRN.Equals(i.Object.GRN), nil + return grn.Equals(i.Object.GRN), nil }) if err != nil { return nil, err @@ -284,8 +286,9 @@ func (i *dummyObjectServer) Write(ctx context.Context, r *object.WriteObjectRequ } func (i *dummyObjectServer) Delete(ctx context.Context, r *object.DeleteObjectRequest) (*object.DeleteObjectResponse, error) { - _, err := i.collection.Delete(ctx, namespaceFromUID(r.GRN), func(i *RawObjectWithHistory) (bool, error) { - if r.GRN.Equals(i.Object.GRN) { + grn := getFullGRN(ctx, r.GRN) + _, err := i.collection.Delete(ctx, namespaceFromUID(grn), func(i *RawObjectWithHistory) (bool, error) { + if grn.Equals(i.Object.GRN) { if r.PreviousVersion != "" && i.Object.Version != r.PreviousVersion { return false, fmt.Errorf("expected the previous version to be %s, but was %s", r.PreviousVersion, i.Object.Version) } @@ -306,7 +309,8 @@ func (i *dummyObjectServer) Delete(ctx context.Context, r *object.DeleteObjectRe } func (i *dummyObjectServer) History(ctx context.Context, r *object.ObjectHistoryRequest) (*object.ObjectHistoryResponse, error) { - obj, _, err := i.findObject(ctx, r.GRN, "") + grn := getFullGRN(ctx, r.GRN) + obj, _, err := i.findObject(ctx, grn, "") if err != nil { return nil, err } @@ -370,3 +374,12 @@ func (i *dummyObjectServer) Search(ctx context.Context, r *object.ObjectSearchRe Results: searchResults, }, nil } + +// This sets the TenantId on the request GRN +func getFullGRN(ctx context.Context, grn *object.GRN) *object.GRN { + if grn.TenantId == 0 { + modifier := store.UserFromContext(ctx) + grn.TenantId = modifier.OrgID + } + return grn +} diff --git a/pkg/services/store/object/tests/common.go b/pkg/services/store/object/tests/common.go index 075f54626b6..1b8afffa11a 100644 --- a/pkg/services/store/object/tests/common.go +++ b/pkg/services/store/object/tests/common.go @@ -6,6 +6,7 @@ import ( apikeygenprefix "github.com/grafana/grafana/pkg/components/apikeygenprefixed" "github.com/grafana/grafana/pkg/server" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" saAPI "github.com/grafana/grafana/pkg/services/serviceaccounts/api" saTests "github.com/grafana/grafana/pkg/services/serviceaccounts/tests" @@ -60,9 +61,14 @@ func createTestContext(t *testing.T) testContext { t.Helper() dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - EnableFeatureToggles: []string{"grpcServer"}, - GRPCServerAddress: "127.0.0.1:0", // :0 for choosing the port automatically + EnableFeatureToggles: []string{ + featuremgmt.FlagGrpcServer, + featuremgmt.FlagObjectStore, + }, + AppModeProduction: false, // required for migrations to run + GRPCServerAddress: "127.0.0.1:0", // :0 for choosing the port automatically }) + _, env := testinfra.StartGrafanaEnv(t, dir, path) authToken, serviceAccountUser := createServiceAccountAdminToken(t, env) diff --git a/pkg/services/store/object/tests/server_integration_test.go b/pkg/services/store/object/tests/server_integration_test.go index cd39e784e21..8ee66e25bfc 100644 --- a/pkg/services/store/object/tests/server_integration_test.go +++ b/pkg/services/store/object/tests/server_integration_test.go @@ -1,8 +1,7 @@ package object_server_tests import ( - "crypto/md5" - "encoding/hex" + "encoding/json" "fmt" "reflect" "testing" @@ -10,15 +9,11 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/store/object" + "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/require" "google.golang.org/grpc/metadata" ) -func createContentsHash(contents []byte) string { - hash := md5.Sum(contents) - return hex.EncodeToString(hash[:]) -} - type rawObjectMatcher struct { grn *object.GRN createdRange []time.Time @@ -38,7 +33,9 @@ type objectVersionMatcher struct { } func timestampInRange(ts int64, tsRange []time.Time) bool { - return ts >= tsRange[0].Unix() && ts <= tsRange[1].Unix() + low := tsRange[0].UnixMilli() - 1 + high := tsRange[1].UnixMilli() + 1 + return ts >= low && ts <= high } func requireObjectMatch(t *testing.T, obj *object.RawObject, m rawObjectMatcher) { @@ -46,16 +43,27 @@ func requireObjectMatch(t *testing.T, obj *object.RawObject, m rawObjectMatcher) require.NotNil(t, obj) mismatches := "" - if m.grn != nil && !obj.GRN.Equals(m.grn) { - mismatches += fmt.Sprintf("expected: %v, actual: %v\n", m.grn, obj.GRN) + if m.grn != nil { + if m.grn.TenantId > 0 && m.grn.TenantId != obj.GRN.TenantId { + mismatches += fmt.Sprintf("expected tenant: %d, actual: %d\n", m.grn.TenantId, obj.GRN.TenantId) + } + if m.grn.Scope != "" && m.grn.Scope != obj.GRN.Scope { + mismatches += fmt.Sprintf("expected Scope: %s, actual: %s\n", m.grn.Scope, obj.GRN.Scope) + } + if m.grn.Kind != "" && m.grn.Kind != obj.GRN.Kind { + mismatches += fmt.Sprintf("expected Kind: %s, actual: %s\n", m.grn.Kind, obj.GRN.Kind) + } + if m.grn.UID != "" && m.grn.UID != obj.GRN.UID { + mismatches += fmt.Sprintf("expected UID: %s, actual: %s\n", m.grn.UID, obj.GRN.UID) + } } if len(m.createdRange) == 2 && !timestampInRange(obj.Created, m.createdRange) { - mismatches += fmt.Sprintf("expected createdBy range: [from %s to %s], actual created: %s\n", m.createdRange[0], m.createdRange[1], time.Unix(obj.Created, 0)) + mismatches += fmt.Sprintf("expected Created range: [from %s to %s], actual created: %s\n", m.createdRange[0], m.createdRange[1], time.UnixMilli(obj.Created)) } if len(m.updatedRange) == 2 && !timestampInRange(obj.Updated, m.updatedRange) { - mismatches += fmt.Sprintf("expected updatedRange range: [from %s to %s], actual updated: %s\n", m.updatedRange[0], m.updatedRange[1], time.Unix(obj.Updated, 0)) + mismatches += fmt.Sprintf("expected Updated range: [from %s to %s], actual updated: %s\n", m.updatedRange[0], m.updatedRange[1], time.UnixMilli(obj.Updated)) } if m.createdBy != "" && m.createdBy != obj.CreatedBy { @@ -66,14 +74,12 @@ func requireObjectMatch(t *testing.T, obj *object.RawObject, m rawObjectMatcher) mismatches += fmt.Sprintf("updatedBy: expected:%s, found:%s\n", m.updatedBy, obj.UpdatedBy) } - if !reflect.DeepEqual(m.body, obj.Body) { - mismatches += fmt.Sprintf("expected body len: %d, actual body len: %d\n", len(m.body), len(obj.Body)) - } - - expectedHash := createContentsHash(m.body) - actualHash := createContentsHash(obj.Body) - if expectedHash != actualHash { - mismatches += fmt.Sprintf("expected body hash: %s, actual body hash: %s\n", expectedHash, actualHash) + if len(m.body) > 0 { + if json.Valid(m.body) { + require.JSONEq(t, string(m.body), string(obj.Body), "expecting same body") + } else if !reflect.DeepEqual(m.body, obj.Body) { + mismatches += fmt.Sprintf("expected body len: %d, actual body len: %d\n", len(m.body), len(obj.Body)) + } } if m.version != nil && *m.version != obj.Version { @@ -92,7 +98,7 @@ func requireVersionMatch(t *testing.T, obj *object.ObjectVersionInfo, m objectVe } if len(m.updatedRange) == 2 && !timestampInRange(obj.Updated, m.updatedRange) { - mismatches += fmt.Sprintf("expected updatedRange range: [from %s to %s], actual updated: %s\n", m.updatedRange[0], m.updatedRange[1], time.Unix(obj.Updated, 0)) + mismatches += fmt.Sprintf("expected updatedRange range: [from %s to %s], actual updated: %s\n", m.updatedRange[0], m.updatedRange[1], time.UnixMilli(obj.Updated)) } if m.updatedBy != "" && m.updatedBy != obj.UpdatedBy { @@ -120,7 +126,7 @@ func TestObjectServer(t *testing.T) { grn := &object.GRN{ Kind: kind, UID: "my-test-entity", - Scope: "entity", + Scope: models.ObjectStoreScopeEntity, } body := []byte("{\"name\":\"John\"}") @@ -159,10 +165,11 @@ func TestObjectServer(t *testing.T) { }) require.NoError(t, err) require.Nil(t, readResp.SummaryJson) + require.NotNil(t, readResp.Object) foundGRN := readResp.Object.GRN require.NotNil(t, foundGRN) - require.NotEqual(t, testCtx.user.OrgID, foundGRN.TenantId) // orgId becomes the tenant id when not set + require.Equal(t, testCtx.user.OrgID, foundGRN.TenantId) // orgId becomes the tenant id when not set require.Equal(t, grn.Scope, foundGRN.Scope) require.Equal(t, grn.Kind, foundGRN.Kind) require.Equal(t, grn.UID, foundGRN.UID) @@ -196,6 +203,12 @@ func TestObjectServer(t *testing.T) { t.Run("should be able to update an object", func(t *testing.T) { before := time.Now() + grn := &object.GRN{ + Kind: kind, + UID: util.GenerateShortUID(), + Scope: models.ObjectStoreScopeEntity, + } + writeReq1 := &object.WriteObjectRequest{ GRN: grn, Body: body, @@ -302,8 +315,9 @@ func TestObjectServer(t *testing.T) { w2, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - UID: uid2, - Kind: kind, + UID: uid2, + Kind: kind, + Scope: grn.Scope, }, Body: body, }) @@ -311,8 +325,9 @@ func TestObjectServer(t *testing.T) { w3, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - UID: uid3, - Kind: kind2, + UID: uid3, + Kind: kind2, + Scope: grn.Scope, }, Body: body, }) @@ -320,8 +335,9 @@ func TestObjectServer(t *testing.T) { w4, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - UID: uid4, - Kind: kind2, + UID: uid4, + Kind: kind2, + Scope: grn.Scope, }, Body: body, })