1
0
mirror of https://github.com/rancher/steve.git synced 2025-04-27 02:51:10 +00:00
steve/pkg/resources/counts/counts_test.go
vardhaman22 dae842ea98 updated wrangler from v2 to v3
also updated k8s dependencies to v0.30.1
2024-06-05 22:53:08 +05:30

305 lines
10 KiB
Go

package counts_test
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/rancher/apiserver/pkg/server"
"github.com/rancher/apiserver/pkg/store/empty"
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/steve/pkg/accesscontrol"
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/clustercache"
"github.com/rancher/steve/pkg/resources/counts"
"github.com/rancher/steve/pkg/schema"
"github.com/rancher/wrangler/v3/pkg/schemas"
"github.com/rancher/wrangler/v3/pkg/summary"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
)
const (
testGroup = "test.k8s.io"
testVersion = "v1"
testResource = "testCRD"
testNotUsedResource = "testNotUsedCRD"
testNewResource = "testNewCRD"
)
func TestWatch(t *testing.T) {
tests := []struct {
name string
event string // the event to send, can be "add", "remove", or "change"
newSchema bool
countsForSchema int
errDesired bool
}{
{
name: "add of known schema",
event: "add",
newSchema: false,
countsForSchema: 2,
errDesired: false,
},
{
name: "add of unknown schema",
event: "add",
newSchema: true,
countsForSchema: 0,
errDesired: true,
},
{
name: "change of known schema",
event: "change",
newSchema: false,
countsForSchema: 0,
errDesired: true,
},
{
name: "change of unknown schema",
event: "change",
newSchema: true,
countsForSchema: 0,
errDesired: true,
},
{
name: "remove of known schema",
event: "remove",
newSchema: false,
countsForSchema: 0,
errDesired: false,
},
{
name: "remove of unknown schema",
event: "remove",
newSchema: true,
countsForSchema: 0,
errDesired: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
testSchema := makeSchema(testResource)
testNotUsedSchema := makeSchema(testNotUsedResource)
testNewSchema := makeSchema(testNewResource)
addGenericPermissionsToSchema(testSchema, "list")
addGenericPermissionsToSchema(testNotUsedSchema, "list")
testSchemas := types.EmptyAPISchemas()
testSchemas.MustAddSchema(*testSchema)
testSchemas.MustAddSchema(*testNotUsedSchema)
testOp := &types.APIRequest{
Schemas: testSchemas,
AccessControl: &server.SchemaBasedAccess{},
Request: &http.Request{},
}
fakeCache := NewFakeClusterCache()
gvk := attributes.GVK(testSchema)
newGVK := attributes.GVK(testNewSchema)
fakeCache.AddSummaryObj(makeSummarizedObject(gvk, "testName1", "testNs", "1"))
counts.Register(testSchemas, fakeCache)
// next, get the channel our results will be delivered on
countSchema := testSchemas.LookupSchema("count")
// channel will stream our events after we call the handlers to simulate/add/remove/change events
resChannel, err := countSchema.Store.Watch(testOp, nil, types.WatchRequest{})
assert.NoError(t, err, "got an error when trying to watch counts, did not expect one")
// call the handlers, triggering the update to receive the event
if test.event == "add" {
var summarizedObject *summary.SummarizedObject
var testGVK schema2.GroupVersionKind
if test.newSchema {
summarizedObject = makeSummarizedObject(newGVK, "testNew", "testNs", "1")
testGVK = newGVK
} else {
summarizedObject = makeSummarizedObject(gvk, "testName2", "testNs", "2")
testGVK = gvk
}
err = fakeCache.addHandler(testGVK, "n/a", summarizedObject)
assert.NoError(t, err, "did not expect error when calling add method")
} else if test.event == "change" {
var summarizedObject *summary.SummarizedObject
var testGVK schema2.GroupVersionKind
var changedSummarizedObject *summary.SummarizedObject
if test.newSchema {
summarizedObject = makeSummarizedObject(newGVK, "testNew", "testNs", "1")
changedSummarizedObject = makeSummarizedObject(newGVK, "testNew", "testNs", "2")
testGVK = newGVK
} else {
summarizedObject = makeSummarizedObject(gvk, "testName1", "testNs", "2")
changedSummarizedObject = makeSummarizedObject(gvk, "testName1", "testNs", "3")
testGVK = gvk
}
err = fakeCache.changeHandler(testGVK, "n/a", changedSummarizedObject, summarizedObject)
assert.NoError(t, err, "did not expect error when calling change method")
} else if test.event == "remove" {
var summarizedObject *summary.SummarizedObject
var testGVK schema2.GroupVersionKind
if test.newSchema {
summarizedObject = makeSummarizedObject(newGVK, "testNew", "testNs", "2")
testGVK = newGVK
} else {
summarizedObject = makeSummarizedObject(gvk, "testName1", "testNs", "2")
testGVK = gvk
}
err = fakeCache.removeHandler(testGVK, "n/a", summarizedObject)
assert.NoError(t, err, "did not expect error when calling add method")
} else {
assert.Failf(t, "unexpected event", "%s is not one of the allowed values of add, change, remove", test.event)
}
// need to call the event handler to force the event to stream
outputCount, err := receiveWithTimeout(resChannel, 100*time.Millisecond)
if test.errDesired {
assert.Errorf(t, err, "expected no value from channel, but got one %+v", outputCount)
} else {
assert.NoError(t, err, "got an error when attempting to get a value from the result channel")
assert.NotNilf(t, outputCount, "expected a new count value, did not get one")
count := outputCount.Object.Object.(counts.Count)
assert.Len(t, count.Counts, 1, "only expected one count event")
itemCount, ok := count.Counts[testResource]
assert.True(t, ok, "expected an item count for %s", testResource)
assert.Equal(t, test.countsForSchema, itemCount.Summary.Count, "expected counts to be correct")
}
})
}
}
// receiveWithTimeout tries to get a value from input within duration. Returns an error if no input was received during that period
func receiveWithTimeout(input chan types.APIEvent, duration time.Duration) (*types.APIEvent, error) {
select {
case value := <-input:
return &value, nil
case <-time.After(duration):
return nil, fmt.Errorf("timeout error, no value received after %f seconds", duration.Seconds())
}
}
// addGenericPermissions grants the specified verb for all namespaces and all resourceNames
func addGenericPermissionsToSchema(schema *types.APISchema, verb string) {
if verb == "create" {
schema.CollectionMethods = append(schema.CollectionMethods, http.MethodPost)
} else if verb == "get" {
schema.ResourceMethods = append(schema.ResourceMethods, http.MethodGet)
} else if verb == "list" || verb == "watch" {
// list and watch use the same permission checks, so we handle in one case
schema.CollectionMethods = append(schema.CollectionMethods, http.MethodGet, http.MethodPost)
} else if verb == "update" {
schema.ResourceMethods = append(schema.ResourceMethods, http.MethodPut)
} else if verb == "delete" {
schema.ResourceMethods = append(schema.ResourceMethods, http.MethodDelete)
} else {
panic(fmt.Sprintf("Can't add generic permissions for verb %s", verb))
}
currentAccess := schema.Attributes["access"].(accesscontrol.AccessListByVerb)
currentAccess[verb] = []accesscontrol.Access{
{
Namespace: "*",
ResourceName: "*",
},
}
}
func makeSchema(resourceType string) *types.APISchema {
return &types.APISchema{
Schema: &schemas.Schema{
ID: resourceType,
CollectionMethods: []string{},
ResourceMethods: []string{},
ResourceFields: map[string]schemas.Field{
"name": {Type: "string"},
"value": {Type: "string"},
},
Attributes: map[string]interface{}{
"group": testGroup,
"version": testVersion,
"kind": resourceType,
"resource": resourceType,
"verbs": []string{"get", "list", "watch", "delete", "update", "create"},
"access": accesscontrol.AccessListByVerb{},
},
},
Store: &empty.Store{},
}
}
type fakeClusterCache struct {
summarizedObjects []*summary.SummarizedObject
addHandler clustercache.Handler
removeHandler clustercache.Handler
changeHandler clustercache.ChangeHandler
}
func NewFakeClusterCache() *fakeClusterCache {
return &fakeClusterCache{
summarizedObjects: []*summary.SummarizedObject{},
addHandler: nil,
removeHandler: nil,
changeHandler: nil,
}
}
func (f *fakeClusterCache) Get(gvk schema2.GroupVersionKind, namespace, name string) (interface{}, bool, error) {
return nil, false, nil
}
func (f *fakeClusterCache) List(gvk schema2.GroupVersionKind) []interface{} {
var retList []interface{}
for _, summaryObj := range f.summarizedObjects {
if summaryObj.GroupVersionKind() != gvk {
// only list the summary objects for the provided gvk
continue
}
retList = append(retList, summaryObj)
}
return retList
}
func (f *fakeClusterCache) OnAdd(ctx context.Context, handler clustercache.Handler) {
f.addHandler = handler
}
func (f *fakeClusterCache) OnRemove(ctx context.Context, handler clustercache.Handler) {
f.removeHandler = handler
}
func (f *fakeClusterCache) OnChange(ctx context.Context, handler clustercache.ChangeHandler) {
f.changeHandler = handler
}
func (f *fakeClusterCache) OnSchemas(schemas *schema.Collection) error {
return nil
}
func (f *fakeClusterCache) AddSummaryObj(summaryObj *summary.SummarizedObject) {
f.summarizedObjects = append(f.summarizedObjects, summaryObj)
}
func makeSummarizedObject(gvk schema2.GroupVersionKind, name string, namespace string, version string) *summary.SummarizedObject {
apiVersion, kind := gvk.ToAPIVersionAndKind()
return &summary.SummarizedObject{
Summary: summary.Summary{
State: "",
Error: false,
Transitioning: false,
},
PartialObjectMetadata: metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
Kind: kind,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
ResourceVersion: version, // any non-zero value should work here. 0 seems to have specific meaning for counts
},
},
}
}