grafana/pkg/storage/unified/apistore/store_test.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)
//}