Skip to content

Commit

Permalink
support pod namespace indexer
Browse files Browse the repository at this point in the history
fix comments

optimize code

small optimization for the namespace scope check
  • Loading branch information
ahutsunshine committed Nov 30, 2023
1 parent 22cb314 commit d8bd150
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 5 deletions.
8 changes: 8 additions & 0 deletions pkg/features/kube_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,12 @@ const (
// will not graduate or be enabled by default in future Kubernetes
// releases.
UserNamespacesPodSecurityStandards featuregate.Feature = "UserNamespacesPodSecurityStandards"

// owner: @ahutsunshine
// beta: v1.29
//
// Allows namespace indexer for namespace scope resources in apiserver cache to accelerate list operations.
StorageNamespaceIndex featuregate.Feature = "StorageNamespaceIndex"
)

func init() {
Expand Down Expand Up @@ -1281,4 +1287,6 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
// features that enable backwards compatibility but are scheduled to be removed
// ...
HPAScaleToZero: {Default: false, PreRelease: featuregate.Alpha},

StorageNamespaceIndex: {Default: true, PreRelease: featuregate.Beta},
}
21 changes: 19 additions & 2 deletions pkg/registry/core/pod/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,15 @@ func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {

// MatchPod returns a generic matcher for a given label and field selector.
func MatchPod(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
var indexFields = []string{"spec.nodeName"}
if utilfeature.DefaultFeatureGate.Enabled(features.StorageNamespaceIndex) {
indexFields = append(indexFields, "metadata.namespace")
}
return storage.SelectionPredicate{
Label: label,
Field: field,
GetAttrs: GetAttrs,
IndexFields: []string{"spec.nodeName"},
IndexFields: indexFields,
}
}

Expand All @@ -311,11 +315,24 @@ func NodeNameIndexFunc(obj interface{}) ([]string, error) {
return []string{pod.Spec.NodeName}, nil
}

// NamespaceIndexFunc return value name of given object.
func NamespaceIndexFunc(obj interface{}) ([]string, error) {
pod, ok := obj.(*api.Pod)
if !ok {
return nil, fmt.Errorf("not a pod")
}
return []string{pod.Namespace}, nil
}

// Indexers returns the indexers for pod storage.
func Indexers() *cache.Indexers {
return &cache.Indexers{
var indexers = cache.Indexers{
storage.FieldIndex("spec.nodeName"): NodeNameIndexFunc,
}
if utilfeature.DefaultFeatureGate.Enabled(features.StorageNamespaceIndex) {
indexers[storage.FieldIndex("metadata.namespace")] = NamespaceIndexFunc
}
return &indexers
}

// ToSelectableFields returns a field set that represents the object
Expand Down
2 changes: 1 addition & 1 deletion staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ func (c *Cacher) listItems(ctx context.Context, listRV uint64, key string, pred
}
return nil, readResourceVersion, "", nil
}
return c.watchCache.WaitUntilFreshAndList(ctx, listRV, pred.MatcherIndex())
return c.watchCache.WaitUntilFreshAndList(ctx, listRV, pred.MatcherIndex(ctx))
}

// GetList implements storage.Interface
Expand Down
18 changes: 17 additions & 1 deletion staging/src/k8s.io/apiserver/pkg/storage/selection_predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ limitations under the License.
package storage

import (
"context"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/request"
)

// AttrFunc returns label and field sets and the uninitialized flag for List or Watch to match.
Expand Down Expand Up @@ -145,11 +148,16 @@ func (s *SelectionPredicate) Empty() bool {
// For any index defined by IndexFields, if a matcher can match only (a subset)
// of objects that return <value> for a given index, a pair (<index name>, <value>)
// wil be returned.
func (s *SelectionPredicate) MatcherIndex() []MatchValue {
func (s *SelectionPredicate) MatcherIndex(ctx context.Context) []MatchValue {
var result []MatchValue
for _, field := range s.IndexFields {
if value, ok := s.Field.RequiresExactMatch(field); ok {
result = append(result, MatchValue{IndexName: FieldIndex(field), Value: value})
} else if field == "metadata.namespace" {
// list pods in the namespace. i.e. /api/v1/namespaces/default/pods
if namespace, isNamespaceScope := isNamespaceScopedRequest(ctx); isNamespaceScope {
result = append(result, MatchValue{IndexName: FieldIndex(field), Value: namespace})
}
}
}
for _, label := range s.IndexLabels {
Expand All @@ -160,6 +168,14 @@ func (s *SelectionPredicate) MatcherIndex() []MatchValue {
return result
}

func isNamespaceScopedRequest(ctx context.Context) (string, bool) {
re, _ := request.RequestInfoFrom(ctx)
if re == nil || len(re.Namespace) == 0 {
return "", false
}
return re.Namespace, true
}

// LabelIndex add prefix for label index.
func LabelIndex(label string) string {
return "l:" + label
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package storage

import (
"context"
"errors"
"reflect"
"testing"
Expand All @@ -25,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/request"
)

type Ignored struct {
Expand Down Expand Up @@ -127,54 +129,130 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
indexLabels []string
indexFields []string
expected []MatchValue
ctx context.Context
}{
"Match nil": {
labelSelector: "name=foo",
fieldSelector: "uid=12345",
indexLabels: []string{"bar"},
indexFields: []string{},
ctx: context.Background(),
expected: nil,
},
"Match field": {
labelSelector: "name=foo",
fieldSelector: "uid=12345",
indexLabels: []string{},
indexFields: []string{"uid"},
ctx: context.Background(),
expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}},
},
"Match field for listing namespace pods without metadata.namespace field selector": {
labelSelector: "",
fieldSelector: "",
indexLabels: []string{},
indexFields: []string{"metadata.namespace"},
ctx: request.WithRequestInfo(context.Background(), &request.RequestInfo{
IsResourceRequest: true,
Path: "/api/v1/namespaces/default/pods",
Verb: "list",
APIPrefix: "api",
APIGroup: "",
APIVersion: "v1",
Namespace: "default",
Resource: "pods",
}),
expected: []MatchValue{{IndexName: FieldIndex("metadata.namespace"), Value: "default"}},
},
"Match field for listing namespace pods with metadata.namespace field selector": {
labelSelector: "",
fieldSelector: "metadata.namespace=kube-system",
indexLabels: []string{},
indexFields: []string{"metadata.namespace"},
ctx: request.WithRequestInfo(context.Background(), &request.RequestInfo{
IsResourceRequest: true,
Path: "/api/v1/namespaces/default/pods",
Verb: "list",
APIPrefix: "api",
APIGroup: "",
APIVersion: "v1",
Namespace: "default",
Resource: "pods",
}),
expected: []MatchValue{{IndexName: FieldIndex("metadata.namespace"), Value: "kube-system"}},
},
"Match field for listing all pods without metadata.namespace field selector": {
labelSelector: "",
fieldSelector: "",
indexLabels: []string{},
indexFields: []string{"metadata.namespace"},
ctx: request.WithRequestInfo(context.Background(), &request.RequestInfo{
IsResourceRequest: true,
Path: "/api/v1/pods",
Verb: "list",
APIPrefix: "api",
APIGroup: "",
APIVersion: "v1",
Namespace: "",
Resource: "pods",
}),
expected: nil,
},
"Match field for listing all pods with metadata.namespace field selector": {
labelSelector: "",
fieldSelector: "metadata.namespace=default",
indexLabels: []string{},
indexFields: []string{"metadata.namespace"},
ctx: request.WithRequestInfo(context.Background(), &request.RequestInfo{
IsResourceRequest: true,
Path: "/api/v1/pods",
Verb: "list",
APIPrefix: "api",
APIGroup: "",
APIVersion: "v1",
Namespace: "default",
Resource: "pods",
}),
expected: []MatchValue{{IndexName: FieldIndex("metadata.namespace"), Value: "default"}},
},
"Match label": {
labelSelector: "name=foo",
fieldSelector: "uid=12345",
indexLabels: []string{"name"},
indexFields: []string{},
ctx: context.Background(),
expected: []MatchValue{{IndexName: LabelIndex("name"), Value: "foo"}},
},
"Match field and label": {
labelSelector: "name=foo",
fieldSelector: "uid=12345",
indexLabels: []string{"name"},
indexFields: []string{"uid"},
ctx: context.Background(),
expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}, {IndexName: LabelIndex("name"), Value: "foo"}},
},
"Negative match field and label": {
labelSelector: "name!=foo",
fieldSelector: "uid!=12345",
indexLabels: []string{"name"},
indexFields: []string{"uid"},
ctx: context.Background(),
expected: nil,
},
"Negative match field and match label": {
labelSelector: "name=foo",
fieldSelector: "uid!=12345",
indexLabels: []string{"name"},
indexFields: []string{"uid"},
ctx: context.Background(),
expected: []MatchValue{{IndexName: LabelIndex("name"), Value: "foo"}},
},
"Negative match label and match field": {
labelSelector: "name!=foo",
fieldSelector: "uid=12345",
indexLabels: []string{"name"},
indexFields: []string{"uid"},
ctx: context.Background(),
expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}},
},
}
Expand All @@ -194,7 +272,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
IndexLabels: testCase.indexLabels,
IndexFields: testCase.indexFields,
}
actual := sp.MatcherIndex()
actual := sp.MatcherIndex(testCase.ctx)
if !reflect.DeepEqual(testCase.expected, actual) {
t.Errorf("%v: expected %v, got %v", name, testCase.expected, actual)
}
Expand Down

0 comments on commit d8bd150

Please sign in to comment.