mirror of
https://github.com/kubernetes/client-go.git
synced 2026-05-17 04:52:18 +00:00
The fake clientset used a slice to store each kind of objects, it's quite slow to init the clientset with massive objects because it checked existence of an object by traversing all objects before adding it, which leads to O(n^2) time complexity. Also, the Create, Update, Get, Delete methods needs to traverse all objects, which affects the time statistic of code that calls them. This patch changed to use a map to store each kind of objects, reduced the time complexity of initializing clientset to O(n) and the Create, Update, Get, Delete to O(1). For example: Before this patch, it took ~29s to init a clientset with 30000 Pods, and 2~4ms to create and get an Pod. After this patch, it took ~50ms to init a clientset with 30000 Pods, and tens of µs to create and get an Pod. Kubernetes-commit: 7e15e31e11e48a6db855e30ca9b07dbce3047577
203 lines
7.3 KiB
Go
203 lines
7.3 KiB
Go
/*
|
|
Copyright 2018 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package fake
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"k8s.io/apimachinery/pkg/api/equality"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/diff"
|
|
)
|
|
|
|
const (
|
|
testGroup = "testgroup"
|
|
testVersion = "testversion"
|
|
testResource = "testkinds"
|
|
testNamespace = "testns"
|
|
testName = "testname"
|
|
testKind = "TestKind"
|
|
testAPIVersion = "testgroup/testversion"
|
|
)
|
|
|
|
var scheme *runtime.Scheme
|
|
|
|
func init() {
|
|
scheme = runtime.NewScheme()
|
|
metav1.AddMetaToScheme(scheme)
|
|
}
|
|
|
|
func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
|
|
return &metav1.PartialObjectMetadata{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: apiVersion,
|
|
Kind: kind,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: namespace,
|
|
Name: name,
|
|
},
|
|
}
|
|
}
|
|
|
|
func newPartialObjectMetadataWithAnnotations(annotations map[string]string) *metav1.PartialObjectMetadata {
|
|
u := newPartialObjectMetadata(testAPIVersion, testKind, testNamespace, testName)
|
|
u.Annotations = annotations
|
|
return u
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
client := NewSimpleMetadataClient(scheme,
|
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
|
newPartialObjectMetadata("group2/version", "TheKind", "ns-foo", "name2-foo"),
|
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
|
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"),
|
|
newPartialObjectMetadata("group2/version", "TheKind", "ns-foo", "name2-baz"),
|
|
)
|
|
listFirst, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).List(context.TODO(), metav1.ListOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := []metav1.PartialObjectMetadata{
|
|
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
|
|
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"),
|
|
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
|
}
|
|
if !equality.Semantic.DeepEqual(listFirst.Items, expected) {
|
|
t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
|
|
}
|
|
}
|
|
|
|
type patchTestCase struct {
|
|
name string
|
|
object runtime.Object
|
|
patchType types.PatchType
|
|
patchBytes []byte
|
|
wantErrMsg string
|
|
expectedPatchedObject runtime.Object
|
|
}
|
|
|
|
func (tc *patchTestCase) runner(t *testing.T) {
|
|
client := NewSimpleMetadataClient(scheme, tc.object)
|
|
resourceInterface := client.Resource(schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}).Namespace(testNamespace)
|
|
|
|
got, recErr := resourceInterface.Patch(context.TODO(), testName, tc.patchType, tc.patchBytes, metav1.PatchOptions{})
|
|
|
|
if err := tc.verifyErr(recErr); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if err := tc.verifyResult(got); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
}
|
|
|
|
// verifyErr verifies that the given error returned from Patch is the error
|
|
// expected by the test case.
|
|
func (tc *patchTestCase) verifyErr(err error) error {
|
|
if tc.wantErrMsg != "" && err == nil {
|
|
return fmt.Errorf("want error, got nil")
|
|
}
|
|
|
|
if tc.wantErrMsg == "" && err != nil {
|
|
return fmt.Errorf("want no error, got %v", err)
|
|
}
|
|
|
|
if err != nil {
|
|
if want, got := tc.wantErrMsg, err.Error(); want != got {
|
|
return fmt.Errorf("incorrect error: want: %q got: %q", want, got)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tc *patchTestCase) verifyResult(result *metav1.PartialObjectMetadata) error {
|
|
if tc.expectedPatchedObject == nil && result == nil {
|
|
return nil
|
|
}
|
|
if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
|
|
return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestPatch(t *testing.T) {
|
|
testCases := []patchTestCase{
|
|
{
|
|
name: "jsonpatch fails with merge type",
|
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
|
patchType: types.StrategicMergePatchType,
|
|
patchBytes: []byte(`[]`),
|
|
wantErrMsg: "invalid JSON document",
|
|
}, {
|
|
name: "jsonpatch works with empty patch",
|
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
|
patchType: types.JSONPatchType,
|
|
// No-op
|
|
patchBytes: []byte(`[]`),
|
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
|
}, {
|
|
name: "jsonpatch works with simple change patch",
|
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
|
patchType: types.JSONPatchType,
|
|
// change spec.foo from bar to foobar
|
|
patchBytes: []byte(`[{"op": "replace", "path": "/metadata/annotations/foo", "value": "foobar"}]`),
|
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "foobar"}),
|
|
}, {
|
|
name: "jsonpatch works with simple addition",
|
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
|
patchType: types.JSONPatchType,
|
|
// add spec.newvalue = dummy
|
|
patchBytes: []byte(`[{"op": "add", "path": "/metadata/annotations/newvalue", "value": "dummy"}]`),
|
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar", "newvalue": "dummy"}),
|
|
}, {
|
|
name: "jsonpatch works with simple deletion",
|
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar", "toremove": "shouldnotbehere"}),
|
|
patchType: types.JSONPatchType,
|
|
// remove spec.newvalue = dummy
|
|
patchBytes: []byte(`[{"op": "remove", "path": "/metadata/annotations/toremove"}]`),
|
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
|
}, {
|
|
name: "strategic merge patch fails with JSONPatch",
|
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
|
patchType: types.StrategicMergePatchType,
|
|
// add spec.newvalue = dummy
|
|
patchBytes: []byte(`[{"op": "add", "path": "/metadata/annotations/newvalue", "value": "dummy"}]`),
|
|
wantErrMsg: "invalid JSON document",
|
|
}, {
|
|
name: "merge patch works with simple replacement",
|
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
|
patchType: types.MergePatchType,
|
|
patchBytes: []byte(`{ "metadata": {"annotations": { "foo": "baz" } } }`),
|
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "baz"}),
|
|
},
|
|
// TODO: Add tests for strategic merge using v1.Pod for example to ensure the test cases
|
|
// demonstrate expected use cases.
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, tc.runner)
|
|
}
|
|
}
|