diff --git a/pkg/client/fake/field_selector_client.go b/pkg/client/fake/field_selector_client.go new file mode 100644 index 0000000000..3d0aaee8a8 --- /dev/null +++ b/pkg/client/fake/field_selector_client.go @@ -0,0 +1,106 @@ +/* +Copyright 2020 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" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +var _ client.Client = &fieldSelectorEnableClient{} + +// NewFieldSelectorEnableClient wrapper a fake client that List method enables field selector +func NewFieldSelectorEnableClient(client client.Client, scheme *runtime.Scheme, obj runtime.Object, field string, indexerFunc client.IndexerFunc) (client.Client, error) { + gvk, err := apiutil.GVKForObject(obj, scheme) + if err != nil { + return nil, err + } + + return &fieldSelectorEnableClient{ + Client: client, + scheme: scheme, + gvk: gvk, + fieldName: field, + fieldValue: indexerFunc, + }, nil +} + +type fieldSelectorEnableClient struct { + client.Client + scheme *runtime.Scheme + + gvk schema.GroupVersionKind + + fieldName string + fieldValue client.IndexerFunc +} + +func (f *fieldSelectorEnableClient) List(ctx context.Context, list runtime.Object, opts ...client.ListOption) error { + if err := f.Client.List(ctx, list, opts...); err != nil { + return err + } + + if len(opts) == 0 { + return nil + } + + objs, err := meta.ExtractList(list) + if err != nil { + return err + } + + if len(objs) == 0 { + return nil + } + + if gvk, err := apiutil.GVKForObject(objs[0], f.scheme); err != nil { + return err + } else if gvk != f.gvk { + return nil + } + + options := client.ListOptions{} + options.ApplyOptions(opts) + + if options.FieldSelector == nil { + return nil + } + + value, ok := options.FieldSelector.RequiresExactMatch(f.fieldName) + if !ok { + return meta.SetList(list, []runtime.Object{}) + } + + filtered := make([]runtime.Object, 0) + for i := range objs { + values := f.fieldValue(objs[i]) + if len(values) != 1 { + continue + } + if values[0] == value { + filtered = append(filtered, objs[i]) + } + } + + return meta.SetList(list, filtered) +} diff --git a/pkg/client/fake/field_selector_client_test.go b/pkg/client/fake/field_selector_client_test.go new file mode 100644 index 0000000000..9178057b3d --- /dev/null +++ b/pkg/client/fake/field_selector_client_test.go @@ -0,0 +1,116 @@ +/* +Copyright 2020 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" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestListByFieldSelector(t *testing.T) { + fakeClient := NewFakeClientWithScheme(scheme.Scheme, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + Spec: corev1.PodSpec{ + NodeName: "node1", + }, + }) + + fieldSelectorClient, err := NewFieldSelectorEnableClient(fakeClient, scheme.Scheme, &corev1.Pod{}, "spec.nodeName", func(o runtime.Object) []string { + po, ok := o.(*corev1.Pod) + if !ok { + return nil + } + + return []string{po.Spec.NodeName} + }) + if err != nil { + t.Fatalf("unexpect init client err: %v", err) + } + + cases := []struct { + name string + opts client.ListOption + expectGot bool + }{ + { + name: "nil fieldSelector", + opts: nil, + expectGot: true, + }, + { + name: "does not match fieldSelector name", + opts: client.MatchingFields(fields.Set{ + "spec.nodeName1": "node1", + }), + expectGot: false, + }, + { + name: "does not match fieldSelector value", + opts: client.MatchingFields(fields.Set{ + "spec.nodeName": "node2", + }), + expectGot: false, + }, + { + name: "match fieldSelector name and value", + opts: client.MatchingFields(fields.Set{ + "spec.nodeName": "node1", + }), + expectGot: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + pods := &corev1.PodList{} + + if tc.opts != nil { + if err := fieldSelectorClient.List(context.TODO(), pods, tc.opts); err != nil { + t.Fatalf("unexpect list err: %v", err) + } + } else { + if err := fieldSelectorClient.List(context.TODO(), pods); err != nil { + t.Fatalf("unexpect list err: %v", err) + } + } + + if tc.expectGot { + if len(pods.Items) == 0 { + t.Fatalf("list expect get Pod, but not") + } + if pods.Items[0].Name != "foo" { + t.Fatalf("list expect get Pod name is foo, but name is %s", pods.Items[0].Name) + } + } else if !tc.expectGot { + if len(pods.Items) > 0 { + t.Fatalf("list expect get nothing, but got %d objects", len(pods.Items)) + } + } + }) + } +}