mirror of https://github.com/grafana/grafana.git
353 lines
12 KiB
Go
353 lines
12 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher_test.go
|
|
// Provenance-includes-license: Apache-2.0
|
|
// Provenance-includes-copyright: The Kubernetes Authors.
|
|
|
|
package apistore_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/fields"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
"k8s.io/apiserver/pkg/apis/example"
|
|
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
|
"k8s.io/apiserver/pkg/storage"
|
|
"k8s.io/apiserver/pkg/storage/storagebackend"
|
|
|
|
claims "github.com/grafana/authlib/types"
|
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
storagetesting "github.com/grafana/grafana/pkg/apiserver/storage/testing"
|
|
"github.com/grafana/grafana/pkg/storage/unified/apistore"
|
|
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
|
)
|
|
|
|
func init() {
|
|
metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)
|
|
utilruntime.Must(example.AddToScheme(scheme))
|
|
utilruntime.Must(examplev1.AddToScheme(scheme))
|
|
|
|
// Make sure there is a user in every context
|
|
storagetesting.NewContext = func() context.Context {
|
|
testUserA := &identity.StaticRequester{
|
|
Type: claims.TypeUser,
|
|
Login: "testuser",
|
|
UserID: 123,
|
|
UserUID: "u123",
|
|
OrgRole: identity.RoleAdmin,
|
|
IsGrafanaAdmin: true, // can do anything
|
|
}
|
|
return identity.WithRequester(context.Background(), testUserA)
|
|
}
|
|
}
|
|
|
|
// GetPodAttrs returns labels and fields of a given object for filtering purposes.
|
|
func GetPodAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
|
pod, ok := obj.(*example.Pod)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("not a pod")
|
|
}
|
|
return labels.Set(pod.Labels), PodToSelectableFields(pod), nil
|
|
}
|
|
|
|
// PodToSelectableFields returns a field set that represents the object
|
|
// TODO: fields are not labels, and the validation rules for them do not apply.
|
|
func PodToSelectableFields(pod *example.Pod) fields.Set {
|
|
// The purpose of allocation with a given number of elements is to reduce
|
|
// amount of allocations needed to create the fields.Set. If you add any
|
|
// field here or the number of object-meta related fields changes, this should
|
|
// be adjusted.
|
|
podSpecificFieldsSet := make(fields.Set, 5)
|
|
podSpecificFieldsSet["spec.nodeName"] = pod.Spec.NodeName
|
|
podSpecificFieldsSet["spec.restartPolicy"] = string(pod.Spec.RestartPolicy)
|
|
podSpecificFieldsSet["status.phase"] = string(pod.Status.Phase)
|
|
return AddObjectMetaFieldsSet(podSpecificFieldsSet, &pod.ObjectMeta, true)
|
|
}
|
|
|
|
func AddObjectMetaFieldsSet(source fields.Set, objectMeta *metav1.ObjectMeta, hasNamespaceField bool) fields.Set {
|
|
source["metadata.name"] = objectMeta.Name
|
|
if hasNamespaceField {
|
|
source["metadata.namespace"] = objectMeta.Namespace
|
|
}
|
|
return source
|
|
}
|
|
|
|
func checkStorageInvariants(s storage.Interface) storagetesting.KeyValidation {
|
|
return func(ctx context.Context, t *testing.T, key string) {
|
|
obj := &example.Pod{}
|
|
err := s.Get(ctx, key, storage.GetOptions{}, obj)
|
|
if err != nil {
|
|
t.Fatalf("Get failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCreate(t *testing.T) {
|
|
ctx, store, destroyFunc, err := testSetup(t)
|
|
defer destroyFunc()
|
|
assert.NoError(t, err)
|
|
storagetesting.RunTestCreate(ctx, t, store, checkStorageInvariants(store))
|
|
}
|
|
|
|
// No TTL support in unifed storage
|
|
// func TestCreateWithTTL(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestCreateWithTTL(ctx, t, store)
|
|
// }
|
|
|
|
func TestCreateWithKeyExist(t *testing.T) {
|
|
ctx, store, destroyFunc, err := testSetup(t)
|
|
defer destroyFunc()
|
|
assert.NoError(t, err)
|
|
storagetesting.RunTestCreateWithKeyExist(ctx, t, store)
|
|
}
|
|
|
|
func TestValidUpdate(t *testing.T) {
|
|
ctx, store, destroyFunc, err := testSetup(t)
|
|
defer destroyFunc()
|
|
assert.NoError(t, err)
|
|
storagetesting.RunTestValidUpdate(ctx, t, store)
|
|
}
|
|
|
|
func TestGet(t *testing.T) {
|
|
ctx, store, destroyFunc, err := testSetup(t)
|
|
defer destroyFunc()
|
|
assert.NoError(t, err)
|
|
storagetesting.RunTestGet(ctx, t, store)
|
|
}
|
|
|
|
func TestUnconditionalDelete(t *testing.T) {
|
|
ctx, store, destroyFunc, err := testSetup(t)
|
|
defer destroyFunc()
|
|
assert.NoError(t, err)
|
|
storagetesting.RunTestUnconditionalDelete(ctx, t, store)
|
|
}
|
|
|
|
func TestConditionalDelete(t *testing.T) {
|
|
ctx, store, destroyFunc, err := testSetup(t)
|
|
defer destroyFunc()
|
|
assert.NoError(t, err)
|
|
storagetesting.RunTestConditionalDelete(ctx, t, store)
|
|
}
|
|
|
|
func TestDeleteWithSuggestion(t *testing.T) {
|
|
ctx, store, destroyFunc, err := testSetup(t)
|
|
defer destroyFunc()
|
|
assert.NoError(t, err)
|
|
storagetesting.RunTestDeleteWithSuggestion(ctx, t, store)
|
|
}
|
|
|
|
func TestDeleteWithSuggestionAndConflict(t *testing.T) {
|
|
ctx, store, destroyFunc, err := testSetup(t)
|
|
defer destroyFunc()
|
|
assert.NoError(t, err)
|
|
storagetesting.RunTestDeleteWithSuggestionAndConflict(ctx, t, store)
|
|
}
|
|
|
|
type resourceClientMock struct {
|
|
resourcepb.ResourceStoreClient
|
|
resourcepb.ResourceIndexClient
|
|
resourcepb.ManagedObjectIndexClient
|
|
resourcepb.BulkStoreClient
|
|
resourcepb.BlobStoreClient
|
|
resourcepb.DiagnosticsClient
|
|
}
|
|
|
|
// always return GRPC Unauthenticated code
|
|
func (r resourceClientMock) List(ctx context.Context, in *resourcepb.ListRequest, opts ...grpc.CallOption) (*resourcepb.ListResponse, error) {
|
|
return &resourcepb.ListResponse{}, status.Error(codes.Unauthenticated, "missing token")
|
|
}
|
|
|
|
func TestGRPCtoHTTPStatusMapping(t *testing.T) {
|
|
t.Run("ensure that GRPC Unauthenticated code gets translated to HTTP StatusUnauthorized", func(t *testing.T) {
|
|
s, _, err := apistore.NewStorage(
|
|
&storagebackend.ConfigForResource{},
|
|
resourceClientMock{},
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
apistore.StorageOptions{})
|
|
require.NoError(t, err)
|
|
|
|
err = s.GetList(context.Background(), "/group/resource.grafana.app/resource/resources/namespace/default", storage.ListOptions{}, nil)
|
|
require.Error(t, err)
|
|
|
|
var statusError *apierrors.StatusError
|
|
ok := errors.As(err, &statusError)
|
|
require.Equal(t, true, ok)
|
|
require.Equal(t, int(statusError.Status().Code), http.StatusUnauthorized)
|
|
})
|
|
}
|
|
|
|
// TODO: this test relies on update
|
|
//func TestDeleteWithSuggestionOfDeletedObject(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestDeleteWithSuggestionOfDeletedObject(ctx, t, store)
|
|
//}
|
|
|
|
//func TestValidateDeletionWithSuggestion(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestValidateDeletionWithSuggestion(ctx, t, store)
|
|
//}
|
|
//
|
|
//func TestPreconditionalDeleteWithSuggestion(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestPreconditionalDeleteWithSuggestion(ctx, t, store)
|
|
//}
|
|
|
|
//func TestList(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestList(ctx, t, store, func(ctx context.Context, t *testing.T, rv string) {
|
|
//
|
|
// }, true)
|
|
//}
|
|
|
|
//func compact(store *Storage) storagetesting.Compaction {
|
|
// return func(ctx context.Context, t *testing.T, resourceVersion string) {
|
|
// // tests expect that the resource version is incremented after compaction:
|
|
// // https://github.com/kubernetes/apiserver/blob/4f7f407e71725f4056328bbeb6d6139843716ca6/pkg/storage/etcd3/compact.go#L137
|
|
// _ = store.getNewResourceVersion()
|
|
// }
|
|
//}
|
|
|
|
//func TestGetListNonRecursive(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestGetListNonRecursive(ctx, t, compact(store.(*Storage)), store)
|
|
//}
|
|
|
|
//func checkStorageCalls(t *testing.T, pageSize, estimatedProcessedObjects uint64) {
|
|
// if reads := getReadsAndReset(); reads != estimatedProcessedObjects {
|
|
// t.Errorf("unexpected reads: %d, expected: %d", reads, estimatedProcessedObjects)
|
|
// }
|
|
// estimatedGetCalls := uint64(1)
|
|
// if pageSize != 0 {
|
|
// // We expect that kube-apiserver will be increasing page sizes
|
|
// // if not full pages are received, so we should see significantly less
|
|
// // than 1000 pages (which would be result of talking to etcd with page size
|
|
// // copied from pred.Limit).
|
|
// // The expected number of calls is n+1 where n is the smallest n so that:
|
|
// // pageSize + pageSize * 2 + pageSize * 4 + ... + pageSize * 2^n >= podCount.
|
|
// // For pageSize = 1, podCount = 1000, we get n+1 = 10, 2 ^ 10 = 1024.
|
|
// currLimit := pageSize
|
|
// for sum := uint64(1); sum < estimatedProcessedObjects; {
|
|
// currLimit *= 2
|
|
// if currLimit > 10000 {
|
|
// currLimit = 10000
|
|
// }
|
|
// sum += currLimit
|
|
// estimatedGetCalls++
|
|
// }
|
|
// }
|
|
// if reads := getReadsAndReset(); reads != estimatedGetCalls {
|
|
// t.Errorf("unexpected reads: %d, expected: %d", reads, estimatedProcessedObjects)
|
|
// }
|
|
//}
|
|
|
|
//func TestListContinuation(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestListContinuation(ctx, t, store, checkStorageCalls)
|
|
//}
|
|
//
|
|
//func TestListPaginationRareObject(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestListPaginationRareObject(ctx, t, store, checkStorageCalls)
|
|
//}
|
|
//
|
|
//func TestListContinuationWithFilter(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestListContinuationWithFilter(ctx, t, store, checkStorageCalls)
|
|
//}
|
|
//
|
|
//func TestListInconsistentContinuation(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestListInconsistentContinuation(ctx, t, store, compact(store.(*Storage)))
|
|
//}
|
|
//
|
|
//func TestConsistentList(t *testing.T) {
|
|
// // TODO(#109831): Enable use of this test and run it.
|
|
//}
|
|
//
|
|
//func TestGuaranteedUpdate(t *testing.T) {
|
|
// // ctx, store, destroyFunc, err := testSetup(t)
|
|
// // defer destroyFunc()
|
|
// // assert.NoError(t, err)
|
|
// // storagetesting.RunTestGuaranteedUpdate(ctx, t, store, nil)
|
|
//}
|
|
//
|
|
//func TestGuaranteedUpdateWithTTL(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestGuaranteedUpdateWithTTL(ctx, t, store)
|
|
//}
|
|
//
|
|
//func TestGuaranteedUpdateChecksStoredData(t *testing.T) {
|
|
// // ctx, store, destroyFunc, err := testSetup(t)
|
|
// // defer destroyFunc()
|
|
// // assert.NoError(t, err)
|
|
// // storagetesting.RunTestGuaranteedUpdateChecksStoredData(ctx, t, store)
|
|
//}
|
|
//
|
|
//func TestGuaranteedUpdateWithConflict(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestGuaranteedUpdateWithConflict(ctx, t, store)
|
|
//}
|
|
//
|
|
//func TestGuaranteedUpdateWithSuggestionAndConflict(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestGuaranteedUpdateWithSuggestionAndConflict(ctx, t, store)
|
|
//}
|
|
|
|
func TestTransformationFailure(t *testing.T) {
|
|
// TODO(#109831): Enable use of this test and run it.
|
|
}
|
|
|
|
//func TestCount(t *testing.T) {
|
|
// ctx, store, destroyFunc, err := testSetup(t)
|
|
// defer destroyFunc()
|
|
// assert.NoError(t, err)
|
|
// storagetesting.RunTestCount(ctx, t, store)
|
|
//}
|