mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-09 20:17:41 +00:00
Merge pull request #121906 from ahutsunshine/namespace-indexer
support namespace indexer for namespaced resources like pods
This commit is contained in:
commit
2eb2a62e15
@ -975,6 +975,12 @@ const (
|
|||||||
// will not graduate or be enabled by default in future Kubernetes
|
// will not graduate or be enabled by default in future Kubernetes
|
||||||
// releases.
|
// releases.
|
||||||
UserNamespacesPodSecurityStandards featuregate.Feature = "UserNamespacesPodSecurityStandards"
|
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() {
|
func init() {
|
||||||
@ -1283,4 +1289,6 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
// features that enable backwards compatibility but are scheduled to be removed
|
// features that enable backwards compatibility but are scheduled to be removed
|
||||||
// ...
|
// ...
|
||||||
HPAScaleToZero: {Default: false, PreRelease: featuregate.Alpha},
|
HPAScaleToZero: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
StorageNamespaceIndex: {Default: true, PreRelease: featuregate.Beta},
|
||||||
}
|
}
|
||||||
|
@ -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.
|
// MatchPod returns a generic matcher for a given label and field selector.
|
||||||
func MatchPod(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
|
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{
|
return storage.SelectionPredicate{
|
||||||
Label: label,
|
Label: label,
|
||||||
Field: field,
|
Field: field,
|
||||||
GetAttrs: GetAttrs,
|
GetAttrs: GetAttrs,
|
||||||
IndexFields: []string{"spec.nodeName"},
|
IndexFields: indexFields,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,11 +315,24 @@ func NodeNameIndexFunc(obj interface{}) ([]string, error) {
|
|||||||
return []string{pod.Spec.NodeName}, nil
|
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.
|
// Indexers returns the indexers for pod storage.
|
||||||
func Indexers() *cache.Indexers {
|
func Indexers() *cache.Indexers {
|
||||||
return &cache.Indexers{
|
var indexers = cache.Indexers{
|
||||||
storage.FieldIndex("spec.nodeName"): NodeNameIndexFunc,
|
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
|
// ToSelectableFields returns a field set that represents the object
|
||||||
|
@ -746,7 +746,7 @@ func (c *Cacher) listItems(ctx context.Context, listRV uint64, key string, pred
|
|||||||
}
|
}
|
||||||
return nil, readResourceVersion, "", nil
|
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
|
// GetList implements storage.Interface
|
||||||
|
@ -17,10 +17,13 @@ limitations under the License.
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"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.
|
// AttrFunc returns label and field sets and the uninitialized flag for List or Watch to match.
|
||||||
@ -145,11 +148,16 @@ func (s *SelectionPredicate) Empty() bool {
|
|||||||
// For any index defined by IndexFields, if a matcher can match only (a subset)
|
// 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>)
|
// of objects that return <value> for a given index, a pair (<index name>, <value>)
|
||||||
// wil be returned.
|
// wil be returned.
|
||||||
func (s *SelectionPredicate) MatcherIndex() []MatchValue {
|
func (s *SelectionPredicate) MatcherIndex(ctx context.Context) []MatchValue {
|
||||||
var result []MatchValue
|
var result []MatchValue
|
||||||
for _, field := range s.IndexFields {
|
for _, field := range s.IndexFields {
|
||||||
if value, ok := s.Field.RequiresExactMatch(field); ok {
|
if value, ok := s.Field.RequiresExactMatch(field); ok {
|
||||||
result = append(result, MatchValue{IndexName: FieldIndex(field), Value: value})
|
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 {
|
for _, label := range s.IndexLabels {
|
||||||
@ -160,6 +168,14 @@ func (s *SelectionPredicate) MatcherIndex() []MatchValue {
|
|||||||
return result
|
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.
|
// LabelIndex add prefix for label index.
|
||||||
func LabelIndex(label string) string {
|
func LabelIndex(label string) string {
|
||||||
return "l:" + label
|
return "l:" + label
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@ -25,6 +26,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ignored struct {
|
type Ignored struct {
|
||||||
@ -127,12 +129,14 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
|
|||||||
indexLabels []string
|
indexLabels []string
|
||||||
indexFields []string
|
indexFields []string
|
||||||
expected []MatchValue
|
expected []MatchValue
|
||||||
|
ctx context.Context
|
||||||
}{
|
}{
|
||||||
"Match nil": {
|
"Match nil": {
|
||||||
labelSelector: "name=foo",
|
labelSelector: "name=foo",
|
||||||
fieldSelector: "uid=12345",
|
fieldSelector: "uid=12345",
|
||||||
indexLabels: []string{"bar"},
|
indexLabels: []string{"bar"},
|
||||||
indexFields: []string{},
|
indexFields: []string{},
|
||||||
|
ctx: context.Background(),
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
"Match field": {
|
"Match field": {
|
||||||
@ -140,13 +144,83 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
|
|||||||
fieldSelector: "uid=12345",
|
fieldSelector: "uid=12345",
|
||||||
indexLabels: []string{},
|
indexLabels: []string{},
|
||||||
indexFields: []string{"uid"},
|
indexFields: []string{"uid"},
|
||||||
|
ctx: context.Background(),
|
||||||
expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}},
|
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": {
|
"Match label": {
|
||||||
labelSelector: "name=foo",
|
labelSelector: "name=foo",
|
||||||
fieldSelector: "uid=12345",
|
fieldSelector: "uid=12345",
|
||||||
indexLabels: []string{"name"},
|
indexLabels: []string{"name"},
|
||||||
indexFields: []string{},
|
indexFields: []string{},
|
||||||
|
ctx: context.Background(),
|
||||||
expected: []MatchValue{{IndexName: LabelIndex("name"), Value: "foo"}},
|
expected: []MatchValue{{IndexName: LabelIndex("name"), Value: "foo"}},
|
||||||
},
|
},
|
||||||
"Match field and label": {
|
"Match field and label": {
|
||||||
@ -154,6 +228,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
|
|||||||
fieldSelector: "uid=12345",
|
fieldSelector: "uid=12345",
|
||||||
indexLabels: []string{"name"},
|
indexLabels: []string{"name"},
|
||||||
indexFields: []string{"uid"},
|
indexFields: []string{"uid"},
|
||||||
|
ctx: context.Background(),
|
||||||
expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}, {IndexName: LabelIndex("name"), Value: "foo"}},
|
expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}, {IndexName: LabelIndex("name"), Value: "foo"}},
|
||||||
},
|
},
|
||||||
"Negative match field and label": {
|
"Negative match field and label": {
|
||||||
@ -161,6 +236,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
|
|||||||
fieldSelector: "uid!=12345",
|
fieldSelector: "uid!=12345",
|
||||||
indexLabels: []string{"name"},
|
indexLabels: []string{"name"},
|
||||||
indexFields: []string{"uid"},
|
indexFields: []string{"uid"},
|
||||||
|
ctx: context.Background(),
|
||||||
expected: nil,
|
expected: nil,
|
||||||
},
|
},
|
||||||
"Negative match field and match label": {
|
"Negative match field and match label": {
|
||||||
@ -168,6 +244,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
|
|||||||
fieldSelector: "uid!=12345",
|
fieldSelector: "uid!=12345",
|
||||||
indexLabels: []string{"name"},
|
indexLabels: []string{"name"},
|
||||||
indexFields: []string{"uid"},
|
indexFields: []string{"uid"},
|
||||||
|
ctx: context.Background(),
|
||||||
expected: []MatchValue{{IndexName: LabelIndex("name"), Value: "foo"}},
|
expected: []MatchValue{{IndexName: LabelIndex("name"), Value: "foo"}},
|
||||||
},
|
},
|
||||||
"Negative match label and match field": {
|
"Negative match label and match field": {
|
||||||
@ -175,6 +252,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
|
|||||||
fieldSelector: "uid=12345",
|
fieldSelector: "uid=12345",
|
||||||
indexLabels: []string{"name"},
|
indexLabels: []string{"name"},
|
||||||
indexFields: []string{"uid"},
|
indexFields: []string{"uid"},
|
||||||
|
ctx: context.Background(),
|
||||||
expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}},
|
expected: []MatchValue{{IndexName: FieldIndex("uid"), Value: "12345"}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -194,7 +272,7 @@ func TestSelectionPredicateMatcherIndex(t *testing.T) {
|
|||||||
IndexLabels: testCase.indexLabels,
|
IndexLabels: testCase.indexLabels,
|
||||||
IndexFields: testCase.indexFields,
|
IndexFields: testCase.indexFields,
|
||||||
}
|
}
|
||||||
actual := sp.MatcherIndex()
|
actual := sp.MatcherIndex(testCase.ctx)
|
||||||
if !reflect.DeepEqual(testCase.expected, actual) {
|
if !reflect.DeepEqual(testCase.expected, actual) {
|
||||||
t.Errorf("%v: expected %v, got %v", name, testCase.expected, actual)
|
t.Errorf("%v: expected %v, got %v", name, testCase.expected, actual)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user