The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/storage/unified/apistore/store_test.go

344 lines
11 KiB

// 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/resource"
)
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.ObjectMeta.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 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 {
resource.ResourceStoreClient
resource.ResourceIndexClient
resource.RepositoryIndexClient
resource.BulkStoreClient
resource.BlobStoreClient
resource.DiagnosticsClient
}
// always return GRPC Unauthenticated code
func (r resourceClientMock) List(ctx context.Context, in *resource.ListRequest, opts ...grpc.CallOption) (*resource.ListResponse, error) {
return &resource.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,
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)
//}