Skip to content

Commit 534d7cb

Browse files
authored
Merge pull request kubernetes#123905 from aojea/field_selector
Field selector for Services based on ClusterIP and Type
2 parents 5a71f37 + 8f306d8 commit 534d7cb

File tree

6 files changed

+608
-2
lines changed

6 files changed

+608
-2
lines changed

pkg/apis/core/v1/conversion.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
9797
if err := AddFieldLabelConversionsForSecret(scheme); err != nil {
9898
return err
9999
}
100+
if err := AddFieldLabelConversionsForService(scheme); err != nil {
101+
return err
102+
}
100103
return nil
101104
}
102105

@@ -488,6 +491,21 @@ func AddFieldLabelConversionsForSecret(scheme *runtime.Scheme) error {
488491
})
489492
}
490493

494+
func AddFieldLabelConversionsForService(scheme *runtime.Scheme) error {
495+
return scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.WithKind("Service"),
496+
func(label, value string) (string, string, error) {
497+
switch label {
498+
case "metadata.namespace",
499+
"metadata.name",
500+
"spec.clusterIP",
501+
"spec.type":
502+
return label, value, nil
503+
default:
504+
return "", "", fmt.Errorf("field label not supported: %s", label)
505+
}
506+
})
507+
}
508+
491509
var initContainerAnnotations = map[string]bool{
492510
"pod.beta.kubernetes.io/init-containers": true,
493511
"pod.alpha.kubernetes.io/init-containers": true,

pkg/kubelet/kubelet.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,11 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
455455
var serviceLister corelisters.ServiceLister
456456
var serviceHasSynced cache.InformerSynced
457457
if kubeDeps.KubeClient != nil {
458-
kubeInformers := informers.NewSharedInformerFactoryWithOptions(kubeDeps.KubeClient, 0)
458+
// don't watch headless services, they are not needed since this informer is only used to create the environment variables for pods.
459+
// See https://issues.k8s.io/122394
460+
kubeInformers := informers.NewSharedInformerFactoryWithOptions(kubeDeps.KubeClient, 0, informers.WithTweakListOptions(func(options *metav1.ListOptions) {
461+
options.FieldSelector = fields.OneTermNotEqualSelector("spec.clusterIP", v1.ClusterIPNone).String()
462+
}))
459463
serviceLister = kubeInformers.Core().V1().Services().Lister()
460464
serviceHasSynced = kubeInformers.Core().V1().Services().Informer().HasSynced
461465
kubeInformers.Start(wait.NeverStop)

pkg/registry/core/service/storage/storage.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func NewREST(
8888
store := &genericregistry.Store{
8989
NewFunc: func() runtime.Object { return &api.Service{} },
9090
NewListFunc: func() runtime.Object { return &api.ServiceList{} },
91+
PredicateFunc: svcreg.Matcher,
9192
DefaultQualifiedResource: api.Resource("services"),
9293
SingularQualifiedResource: api.Resource("service"),
9394
ReturnDeletedObject: true,
@@ -99,7 +100,10 @@ func NewREST(
99100

100101
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
101102
}
102-
options := &generic.StoreOptions{RESTOptions: optsGetter}
103+
options := &generic.StoreOptions{
104+
RESTOptions: optsGetter,
105+
AttrFunc: svcreg.GetAttrs,
106+
}
103107
if err := store.CompleteWithOptions(options); err != nil {
104108
return nil, nil, nil, err
105109
}

pkg/registry/core/service/strategy.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ package service
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"reflect"
2223

24+
"k8s.io/apimachinery/pkg/fields"
25+
"k8s.io/apimachinery/pkg/labels"
2326
"k8s.io/apimachinery/pkg/runtime"
2427
"k8s.io/apimachinery/pkg/util/sets"
2528
"k8s.io/apimachinery/pkg/util/validation/field"
29+
"k8s.io/apiserver/pkg/registry/generic"
30+
pkgstorage "k8s.io/apiserver/pkg/storage"
2631
"k8s.io/apiserver/pkg/storage/names"
2732
utilfeature "k8s.io/apiserver/pkg/util/feature"
2833
"k8s.io/kubernetes/pkg/api/legacyscheme"
@@ -166,6 +171,34 @@ func (serviceStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runt
166171
return nil
167172
}
168173

174+
// GetAttrs returns labels and fields of a given object for filtering purposes.
175+
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
176+
service, ok := obj.(*api.Service)
177+
if !ok {
178+
return nil, nil, fmt.Errorf("not a service")
179+
}
180+
return service.Labels, SelectableFields(service), nil
181+
}
182+
183+
// Matcher returns a selection predicate for a given label and field selector.
184+
func Matcher(label labels.Selector, field fields.Selector) pkgstorage.SelectionPredicate {
185+
return pkgstorage.SelectionPredicate{
186+
Label: label,
187+
Field: field,
188+
GetAttrs: GetAttrs,
189+
}
190+
}
191+
192+
// SelectableFields returns a field set that can be used for filter selection
193+
func SelectableFields(service *api.Service) fields.Set {
194+
objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&service.ObjectMeta, true)
195+
serviceSpecificFieldsSet := fields.Set{
196+
"spec.clusterIP": service.Spec.ClusterIP,
197+
"spec.type": string(service.Spec.Type),
198+
}
199+
return generic.MergeFieldsSets(objectMetaFieldsSet, serviceSpecificFieldsSet)
200+
}
201+
169202
// dropServiceStatusDisabledFields drops fields that are not used if their associated feature gates
170203
// are not enabled. The typical pattern is:
171204
//

pkg/registry/core/service/strategy_test.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"github.com/google/go-cmp/cmp"
2424
"k8s.io/apimachinery/pkg/api/errors"
2525
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/fields"
27+
"k8s.io/apimachinery/pkg/labels"
2628
"k8s.io/apimachinery/pkg/util/intstr"
2729
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
2830
"k8s.io/apiserver/pkg/registry/rest"
@@ -785,3 +787,159 @@ func TestDropTypeDependentFields(t *testing.T) {
785787
})
786788
}
787789
}
790+
791+
func TestMatchService(t *testing.T) {
792+
testCases := []struct {
793+
name string
794+
in *api.Service
795+
fieldSelector fields.Selector
796+
expectMatch bool
797+
}{
798+
{
799+
name: "match on name",
800+
in: &api.Service{
801+
ObjectMeta: metav1.ObjectMeta{
802+
Name: "test",
803+
Namespace: "testns",
804+
},
805+
Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone},
806+
},
807+
fieldSelector: fields.ParseSelectorOrDie("metadata.name=test"),
808+
expectMatch: true,
809+
},
810+
{
811+
name: "match on namespace",
812+
in: &api.Service{
813+
ObjectMeta: metav1.ObjectMeta{
814+
Name: "test",
815+
Namespace: "testns",
816+
},
817+
Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone},
818+
},
819+
fieldSelector: fields.ParseSelectorOrDie("metadata.namespace=testns"),
820+
expectMatch: true,
821+
},
822+
{
823+
name: "no match on name",
824+
in: &api.Service{
825+
ObjectMeta: metav1.ObjectMeta{
826+
Name: "test",
827+
Namespace: "testns",
828+
},
829+
Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone},
830+
},
831+
fieldSelector: fields.ParseSelectorOrDie("metadata.name=nomatch"),
832+
expectMatch: false,
833+
},
834+
{
835+
name: "no match on namespace",
836+
in: &api.Service{
837+
ObjectMeta: metav1.ObjectMeta{
838+
Name: "test",
839+
Namespace: "testns",
840+
},
841+
Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone},
842+
},
843+
fieldSelector: fields.ParseSelectorOrDie("metadata.namespace=nomatch"),
844+
expectMatch: false,
845+
},
846+
{
847+
name: "match on loadbalancer type service",
848+
in: &api.Service{
849+
Spec: api.ServiceSpec{Type: api.ServiceTypeLoadBalancer},
850+
},
851+
fieldSelector: fields.ParseSelectorOrDie("spec.type=LoadBalancer"),
852+
expectMatch: true,
853+
},
854+
{
855+
name: "no match on nodeport type service",
856+
in: &api.Service{
857+
Spec: api.ServiceSpec{Type: api.ServiceTypeNodePort},
858+
},
859+
fieldSelector: fields.ParseSelectorOrDie("spec.type=LoadBalancer"),
860+
expectMatch: false,
861+
},
862+
{
863+
name: "match on headless service",
864+
in: &api.Service{
865+
Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone},
866+
},
867+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=None"),
868+
expectMatch: true,
869+
},
870+
{
871+
name: "no match on clusterIP service",
872+
in: &api.Service{
873+
Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"},
874+
},
875+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=None"),
876+
expectMatch: false,
877+
},
878+
{
879+
name: "match on clusterIP service",
880+
in: &api.Service{
881+
Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"},
882+
},
883+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=192.168.1.1"),
884+
expectMatch: true,
885+
},
886+
{
887+
name: "match on non-headless service",
888+
in: &api.Service{
889+
Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"},
890+
},
891+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP!=None"),
892+
expectMatch: true,
893+
},
894+
{
895+
name: "match on any ClusterIP set service",
896+
in: &api.Service{
897+
Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"},
898+
},
899+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP!=\"\""),
900+
expectMatch: true,
901+
},
902+
{
903+
name: "match on clusterIP IPv6 service",
904+
in: &api.Service{
905+
Spec: api.ServiceSpec{ClusterIP: "2001:db2::1"},
906+
},
907+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=2001:db2::1"),
908+
expectMatch: true,
909+
},
910+
{
911+
name: "no match on headless service",
912+
in: &api.Service{
913+
Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone},
914+
},
915+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=192.168.1.1"),
916+
expectMatch: false,
917+
},
918+
{
919+
name: "no match on headless service",
920+
in: &api.Service{
921+
Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone},
922+
},
923+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=2001:db2::1"),
924+
expectMatch: false,
925+
},
926+
{
927+
name: "no match on empty service",
928+
in: &api.Service{},
929+
fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=None"),
930+
expectMatch: false,
931+
},
932+
}
933+
for _, testCase := range testCases {
934+
t.Run(testCase.name, func(t *testing.T) {
935+
m := Matcher(labels.Everything(), testCase.fieldSelector)
936+
result, err := m.Matches(testCase.in)
937+
if err != nil {
938+
t.Errorf("Unexpected error %v", err)
939+
}
940+
if result != testCase.expectMatch {
941+
t.Errorf("Result %v, Expected %v, Selector: %v, Service: %v", result, testCase.expectMatch, testCase.fieldSelector.String(), testCase.in)
942+
}
943+
})
944+
}
945+
}

0 commit comments

Comments
 (0)