mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 22:40:35 +00:00
feat: Add common library for declared feature discovery, inference and matching.
This commit is contained in:
@@ -33,6 +33,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
dir: testing
|
||||
pkgname: testing
|
||||
template: testify
|
||||
template-data:
|
||||
boilerplate-file: ../../../../../hack/boilerplate/boilerplate.generatego.txt
|
||||
packages:
|
||||
k8s.io/component-helpers/nodedeclaredfeatures:
|
||||
config:
|
||||
filename: mocks.go # New file name
|
||||
interfaces:
|
||||
Feature: {}
|
||||
FeatureGate: {}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2025 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 features
|
||||
|
||||
import (
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures"
|
||||
)
|
||||
|
||||
// AllFeatures is the central registry for all declared features.
|
||||
// New features are added to this list to be automatically included in both
|
||||
// discovery and inference logic.
|
||||
var AllFeatures = []nodedeclaredfeatures.Feature{}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
Copyright 2025 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 nodedeclaredfeatures
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
)
|
||||
|
||||
// Framework provides functions for discovering node features and inferring pod feature requirements.
|
||||
// It is stateful and holds the feature registry.
|
||||
type Framework struct {
|
||||
registry []Feature
|
||||
}
|
||||
|
||||
// FeatureSet is a set of node features.
|
||||
type FeatureSet struct {
|
||||
sets.Set[string]
|
||||
}
|
||||
|
||||
// NewFeatureSet creates a FeatureSet from a list of feature names.
|
||||
func NewFeatureSet(features ...string) FeatureSet {
|
||||
return FeatureSet{Set: sets.New(features...)}
|
||||
}
|
||||
|
||||
// Equal returns true if both the sets have the same features.
|
||||
func (s *FeatureSet) Equal(other FeatureSet) bool {
|
||||
return s.Set.Equal(other.Set)
|
||||
}
|
||||
|
||||
// Clone returns a copy of the FeatureSet.
|
||||
func (s *FeatureSet) Clone() FeatureSet {
|
||||
if s.Set == nil {
|
||||
return FeatureSet{Set: nil}
|
||||
}
|
||||
return FeatureSet{Set: s.Set.Clone()}
|
||||
}
|
||||
|
||||
// New creates a new instance of the Framework.
|
||||
func New(registry []Feature) (*Framework, error) {
|
||||
if registry == nil {
|
||||
return nil, fmt.Errorf("registry must not be nil")
|
||||
}
|
||||
return &Framework{
|
||||
registry: registry,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DiscoverNodeFeatures determines which features from the registry are enabled
|
||||
// for a specific node configuration. It returns a sorted, unique list of feature names.
|
||||
func (f *Framework) DiscoverNodeFeatures(cfg *NodeConfiguration) []string {
|
||||
var enabledFeatures []string
|
||||
for _, f := range f.registry {
|
||||
if f.Discover(cfg) {
|
||||
if cfg.Version != nil && f.MaxVersion() != nil && cfg.Version.GreaterThan(f.MaxVersion()) {
|
||||
continue
|
||||
}
|
||||
enabledFeatures = append(enabledFeatures, f.Name())
|
||||
}
|
||||
}
|
||||
slices.Sort(enabledFeatures)
|
||||
return enabledFeatures
|
||||
}
|
||||
|
||||
// InferForPodScheduling determines which features from the registry are required by a pod scheduling for a given target version.
|
||||
func (f *Framework) InferForPodScheduling(podInfo *PodInfo, targetVersion *version.Version) (FeatureSet, error) {
|
||||
if targetVersion == nil {
|
||||
return FeatureSet{}, fmt.Errorf("target version cannot be nil")
|
||||
}
|
||||
reqs := NewFeatureSet()
|
||||
for _, f := range f.registry {
|
||||
if f.MaxVersion() != nil && targetVersion.GreaterThan(f.MaxVersion()) {
|
||||
// If target version is greater than the feature's max version, no need to require the feature
|
||||
continue
|
||||
}
|
||||
if f.InferForScheduling(podInfo) {
|
||||
reqs.Insert(f.Name())
|
||||
}
|
||||
}
|
||||
return reqs, nil
|
||||
}
|
||||
|
||||
// InferForPodUpdate determines which features are required by a pod update operation for a given target version.
|
||||
func (f *Framework) InferForPodUpdate(oldPodInfo, newPodInfo *PodInfo, targetVersion *version.Version) (FeatureSet, error) {
|
||||
if targetVersion == nil {
|
||||
return FeatureSet{}, fmt.Errorf("target version cannot be nil")
|
||||
}
|
||||
reqs := NewFeatureSet()
|
||||
for _, f := range f.registry {
|
||||
if f.MaxVersion() != nil && targetVersion.GreaterThan(f.MaxVersion()) {
|
||||
// If target version is greater than the feature's max version, no need to require the feature
|
||||
continue
|
||||
}
|
||||
if f.InferForUpdate(oldPodInfo, newPodInfo) {
|
||||
reqs.Insert(f.Name())
|
||||
}
|
||||
}
|
||||
return reqs, nil
|
||||
}
|
||||
|
||||
// MatchResult encapsulates the result of a feature match check.
|
||||
type MatchResult struct {
|
||||
// IsMatch is true if the node satisfies all feature requirements.
|
||||
IsMatch bool
|
||||
// UnsatisfiedRequirements lists the specific features that were not met.
|
||||
// This field is only populated if IsMatch is false.
|
||||
UnsatisfiedRequirements []string
|
||||
}
|
||||
|
||||
// MatchNode checks if a node's declared features satisfy the pod's pre-computed requirements.
|
||||
// It returns a MatchResult:
|
||||
// - IsMatch is true if all requiredFeatures are present in node.status.declaredFeatures.
|
||||
// - UnsatisfiedRequirements lists features in requiredFeatures but not in node.status.declaredFeatures.
|
||||
func MatchNode(requiredFeatures FeatureSet, node *v1.Node) (*MatchResult, error) {
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("node cannot be nil")
|
||||
}
|
||||
return MatchNodeFeatureSet(requiredFeatures, NewFeatureSet(node.Status.DeclaredFeatures...))
|
||||
}
|
||||
|
||||
// MatchNodeFeatureSet compares a set of required features against a set of features present on a node.
|
||||
// It returns a MatchResult:
|
||||
// - IsMatch is true if all requiredFeatures are present in nodeFeatures.
|
||||
// - UnsatisfiedRequirements lists features in requiredFeatures but not in nodeFeatures.
|
||||
func MatchNodeFeatureSet(requiredFeatures FeatureSet, nodeFeatures FeatureSet) (*MatchResult, error) {
|
||||
if requiredFeatures.Len() == 0 {
|
||||
return &MatchResult{IsMatch: true}, nil // No requirements to match.
|
||||
}
|
||||
var mismatched []string
|
||||
for req := range requiredFeatures.Set {
|
||||
if !nodeFeatures.Has(req) {
|
||||
mismatched = append(mismatched, req)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(mismatched) > 0 {
|
||||
return &MatchResult{IsMatch: false, UnsatisfiedRequirements: mismatched}, nil
|
||||
}
|
||||
return &MatchResult{IsMatch: true}, nil
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
Copyright 2025 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 nodedeclaredfeatures
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
)
|
||||
|
||||
// mockFeature is a mock implementation of the Feature interface for testing.
|
||||
type mockFeature struct {
|
||||
name string
|
||||
discover func(cfg *NodeConfiguration) bool
|
||||
inferForScheduling func(podInfo *PodInfo) bool
|
||||
inferForUpdate func(oldPodInfo, newPodInfo *PodInfo) bool
|
||||
maxVersion *version.Version
|
||||
}
|
||||
|
||||
func (f *mockFeature) Name() string { return f.name }
|
||||
func (f *mockFeature) Discover(cfg *NodeConfiguration) bool { return f.discover(cfg) }
|
||||
func (f *mockFeature) InferForScheduling(podInfo *PodInfo) bool { return f.inferForScheduling(podInfo) }
|
||||
func (f *mockFeature) InferForUpdate(oldPodInfo, newPodInfo *PodInfo) bool {
|
||||
return f.inferForUpdate(oldPodInfo, newPodInfo)
|
||||
}
|
||||
func (f *mockFeature) MaxVersion() *version.Version { return f.maxVersion }
|
||||
|
||||
type mockFeatureGate struct {
|
||||
features map[string]bool
|
||||
}
|
||||
|
||||
func (m *mockFeatureGate) Enabled(key string) bool {
|
||||
if m.features == nil {
|
||||
return false
|
||||
}
|
||||
return m.features[key]
|
||||
}
|
||||
|
||||
func newMockFeatureGate(features map[string]bool) *mockFeatureGate {
|
||||
return &mockFeatureGate{features: features}
|
||||
}
|
||||
|
||||
func TestNewFramework(t *testing.T) {
|
||||
_, err := New(nil)
|
||||
require.Error(t, err, "NewFramework should return an error with a nil registry")
|
||||
|
||||
_, err = New([]Feature{})
|
||||
require.NoError(t, err, "NewFramework should not return an error with an empty registry")
|
||||
}
|
||||
|
||||
func TestDiscoverNodeFeatures(t *testing.T) {
|
||||
featureMaxVersion := version.MustParse("1.38.0")
|
||||
registry := []Feature{
|
||||
&mockFeature{
|
||||
name: "FeatureA",
|
||||
discover: func(cfg *NodeConfiguration) bool {
|
||||
return cfg.FeatureGates.Enabled("feature-a")
|
||||
},
|
||||
maxVersion: featureMaxVersion,
|
||||
},
|
||||
&mockFeature{
|
||||
name: "FeatureBWithStaticConfig",
|
||||
discover: func(cfg *NodeConfiguration) bool {
|
||||
return cfg.FeatureGates.Enabled("feature-b") && cfg.StaticConfig.CPUManagerPolicy == "static"
|
||||
},
|
||||
maxVersion: featureMaxVersion,
|
||||
},
|
||||
}
|
||||
|
||||
framework, _ := New(registry)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config *NodeConfiguration
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "Feature Enabled",
|
||||
config: &NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{string("feature-a"): true}),
|
||||
StaticConfig: StaticConfiguration{},
|
||||
},
|
||||
expected: []string{"FeatureA"},
|
||||
},
|
||||
{
|
||||
name: "multiple features enabled",
|
||||
config: &NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{
|
||||
string("feature-a"): true,
|
||||
string("feature-b"): true,
|
||||
}),
|
||||
StaticConfig: StaticConfiguration{CPUManagerPolicy: "static"},
|
||||
},
|
||||
expected: []string{"FeatureA", "FeatureBWithStaticConfig"}, // Should be sorted
|
||||
},
|
||||
{
|
||||
name: "no features enabled",
|
||||
config: &NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{
|
||||
string("feature-a"): false,
|
||||
string("feature-b"): true,
|
||||
}),
|
||||
StaticConfig: StaticConfiguration{CPUManagerPolicy: "none"},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "feature past max version",
|
||||
config: &NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{string("feature-a"): true}),
|
||||
StaticConfig: StaticConfiguration{},
|
||||
Version: featureMaxVersion.AddMinor(1),
|
||||
},
|
||||
expected: []string{}, // Not published
|
||||
},
|
||||
{
|
||||
name: "feature past max version - pre-release version",
|
||||
config: &NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{string("feature-a"): true}),
|
||||
StaticConfig: StaticConfiguration{},
|
||||
Version: version.MustParse("1.39.0-alpha.2.39+049eafd34dfbd2"),
|
||||
},
|
||||
expected: []string{}, // Not published
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
features := framework.DiscoverNodeFeatures(tc.config)
|
||||
if len(tc.expected) == 0 {
|
||||
assert.Empty(t, features)
|
||||
} else {
|
||||
assert.Equal(t, tc.expected, features)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferForPodScheduling(t *testing.T) {
|
||||
inferPodlevelResources := func(p *PodInfo) bool {
|
||||
return p.Spec.Resources != nil
|
||||
}
|
||||
podWithPodLevelResources := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod-with-podlevelresources"},
|
||||
Spec: v1.PodSpec{
|
||||
Resources: &v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("500m"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
podWithoutPodLevelResources := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod-without-podlevelresources"},
|
||||
Spec: v1.PodSpec{},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
registry []Feature
|
||||
newPod *v1.Pod
|
||||
targetVersion *version.Version
|
||||
expectedReqs FeatureSet
|
||||
expectErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "pod with feature, inferred during scheduling",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
},
|
||||
},
|
||||
newPod: podWithPodLevelResources,
|
||||
targetVersion: version.MustParse("1.30.0"),
|
||||
expectedReqs: NewFeatureSet("PodLevelResources"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "pod without feature",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
},
|
||||
},
|
||||
newPod: podWithoutPodLevelResources,
|
||||
targetVersion: version.MustParse("1.30.0"),
|
||||
expectedReqs: NewFeatureSet(),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "feature universally available, not inferred during create",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
maxVersion: version.MustParse("1.30.0"),
|
||||
},
|
||||
},
|
||||
newPod: podWithPodLevelResources,
|
||||
targetVersion: version.MustParse("1.31.0"),
|
||||
expectedReqs: NewFeatureSet(),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "pre-release target version",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
maxVersion: version.MustParse("1.30.0"),
|
||||
},
|
||||
},
|
||||
newPod: podWithPodLevelResources,
|
||||
targetVersion: version.MustParse("0.0.0-alpha.2.39+049eafd34dfbd2"),
|
||||
expectedReqs: NewFeatureSet("PodLevelResources"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "target version nil",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
},
|
||||
},
|
||||
newPod: podWithPodLevelResources,
|
||||
targetVersion: nil,
|
||||
expectedReqs: NewFeatureSet(),
|
||||
expectErr: true,
|
||||
errContains: "target version cannot be nil",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
framework, _ := New(tc.registry)
|
||||
reqs, err := framework.InferForPodScheduling(&PodInfo{Spec: &tc.newPod.Spec, Status: &tc.newPod.Status}, tc.targetVersion)
|
||||
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tc.errContains)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedReqs, reqs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferForPodUpdate(t *testing.T) {
|
||||
inferResize := func(oldPodInfo, newPodInfo *PodInfo) bool {
|
||||
oldCPU := oldPodInfo.Spec.Containers[0].Resources.Requests.Cpu()
|
||||
newCPU := newPodInfo.Spec.Containers[0].Resources.Requests.Cpu()
|
||||
if oldCPU != nil && newCPU != nil && !oldCPU.Equal(*newCPU) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
basePod := func(cpuRequest string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "c1",
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse(cpuRequest)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
podWith1CPU := basePod("1")
|
||||
podWith2CPU := basePod("2")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
registry []Feature
|
||||
oldPod *v1.Pod
|
||||
newPod *v1.Pod
|
||||
targetVersion *version.Version
|
||||
expectedReqs FeatureSet
|
||||
expectErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "pod update requires feature",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
},
|
||||
},
|
||||
oldPod: podWith1CPU,
|
||||
newPod: podWith2CPU,
|
||||
targetVersion: version.MustParse("1.30.0"),
|
||||
expectedReqs: NewFeatureSet("InPlacePodResize"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "pod update requires feature with pre-release version",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
},
|
||||
},
|
||||
oldPod: podWith1CPU,
|
||||
newPod: podWith2CPU,
|
||||
targetVersion: version.MustParse("1.35.0-alpha.2.39+049eafd34dfbd2"),
|
||||
expectedReqs: NewFeatureSet("InPlacePodResize"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "pod update does not require feature",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
},
|
||||
},
|
||||
oldPod: podWith1CPU,
|
||||
newPod: podWith1CPU,
|
||||
targetVersion: version.MustParse("1.30.0"),
|
||||
expectedReqs: NewFeatureSet(),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "feature universally available, not inferred during update",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
maxVersion: version.MustParse("1.30.0"),
|
||||
},
|
||||
},
|
||||
oldPod: podWith1CPU,
|
||||
newPod: podWith2CPU,
|
||||
targetVersion: version.MustParse("1.31.0"),
|
||||
expectedReqs: NewFeatureSet(),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "target version nil",
|
||||
registry: []Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
},
|
||||
},
|
||||
oldPod: podWith1CPU,
|
||||
newPod: podWith2CPU,
|
||||
targetVersion: nil,
|
||||
expectedReqs: NewFeatureSet(),
|
||||
expectErr: true,
|
||||
errContains: "target version cannot be nil",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
framework, _ := New(tc.registry)
|
||||
reqs, err := framework.InferForPodUpdate(&PodInfo{Spec: &tc.oldPod.Spec, Status: &tc.oldPod.Status}, &PodInfo{Spec: &tc.newPod.Spec, Status: &tc.newPod.Status}, tc.targetVersion)
|
||||
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tc.errContains)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedReqs, reqs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchNode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
podFeatureRequirements FeatureSet
|
||||
nodeFeatures []string
|
||||
expectedMatch bool
|
||||
expectedUnsatisfied []string
|
||||
}{
|
||||
{
|
||||
name: "all features match",
|
||||
podFeatureRequirements: NewFeatureSet("feature-a", "feature-b"),
|
||||
nodeFeatures: []string{"feature-a", "feature-b", "feature-c"},
|
||||
expectedMatch: true,
|
||||
expectedUnsatisfied: nil,
|
||||
},
|
||||
{
|
||||
name: "some features missing",
|
||||
podFeatureRequirements: NewFeatureSet("feature-a", "feature-b"),
|
||||
nodeFeatures: []string{"feature-a", "feature-c"},
|
||||
expectedMatch: false,
|
||||
expectedUnsatisfied: []string{"feature-b"},
|
||||
},
|
||||
{
|
||||
name: "all features missing",
|
||||
podFeatureRequirements: NewFeatureSet("feature-a", "feature-b"),
|
||||
nodeFeatures: []string{"feature-c"},
|
||||
expectedMatch: false,
|
||||
expectedUnsatisfied: []string{"feature-a", "feature-b"},
|
||||
},
|
||||
{
|
||||
name: "no node features",
|
||||
podFeatureRequirements: NewFeatureSet("feature-a", "feature-b"),
|
||||
nodeFeatures: []string{},
|
||||
expectedMatch: false,
|
||||
expectedUnsatisfied: []string{"feature-a", "feature-b"},
|
||||
},
|
||||
{
|
||||
name: "no requirements",
|
||||
podFeatureRequirements: NewFeatureSet(),
|
||||
nodeFeatures: []string{"feature-a", "feature-b", "feature-c"},
|
||||
expectedMatch: true,
|
||||
expectedUnsatisfied: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
matchVariations := []string{"MatchNode", "MatchNodeFeatureSet"}
|
||||
for _, variationName := range matchVariations {
|
||||
t.Run(variationName, func(t *testing.T) {
|
||||
var result *MatchResult
|
||||
var err error
|
||||
|
||||
switch variationName {
|
||||
case "MatchNode":
|
||||
node := &v1.Node{Status: v1.NodeStatus{DeclaredFeatures: tc.nodeFeatures}}
|
||||
result, err = MatchNode(tc.podFeatureRequirements, node)
|
||||
case "MatchNodeFeatureSet":
|
||||
result, err = MatchNodeFeatureSet(tc.podFeatureRequirements, NewFeatureSet(tc.nodeFeatures...))
|
||||
default:
|
||||
t.Fatalf("unknown match variation: %s", variationName)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedMatch, result.IsMatch)
|
||||
if !tc.expectedMatch {
|
||||
assert.ElementsMatch(t, tc.expectedUnsatisfied, result.UnsatisfiedRequirements)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test nil node
|
||||
_, err := MatchNode(NewFeatureSet("feature-a"), nil)
|
||||
require.Error(t, err, "MatchNode should return an error for a nil node")
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures"
|
||||
)
|
||||
|
||||
// NewMockFeature creates a new instance of MockFeature. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockFeature(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockFeature {
|
||||
mock := &MockFeature{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// MockFeature is an autogenerated mock type for the Feature type
|
||||
type MockFeature struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockFeature_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockFeature) EXPECT() *MockFeature_Expecter {
|
||||
return &MockFeature_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Discover provides a mock function for the type MockFeature
|
||||
func (_mock *MockFeature) Discover(cfg *nodedeclaredfeatures.NodeConfiguration) bool {
|
||||
ret := _mock.Called(cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Discover")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if returnFunc, ok := ret.Get(0).(func(*nodedeclaredfeatures.NodeConfiguration) bool); ok {
|
||||
r0 = returnFunc(cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockFeature_Discover_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Discover'
|
||||
type MockFeature_Discover_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Discover is a helper method to define mock.On call
|
||||
// - cfg *nodedeclaredfeatures.NodeConfiguration
|
||||
func (_e *MockFeature_Expecter) Discover(cfg interface{}) *MockFeature_Discover_Call {
|
||||
return &MockFeature_Discover_Call{Call: _e.mock.On("Discover", cfg)}
|
||||
}
|
||||
|
||||
func (_c *MockFeature_Discover_Call) Run(run func(cfg *nodedeclaredfeatures.NodeConfiguration)) *MockFeature_Discover_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 *nodedeclaredfeatures.NodeConfiguration
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(*nodedeclaredfeatures.NodeConfiguration)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_Discover_Call) Return(b bool) *MockFeature_Discover_Call {
|
||||
_c.Call.Return(b)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_Discover_Call) RunAndReturn(run func(cfg *nodedeclaredfeatures.NodeConfiguration) bool) *MockFeature_Discover_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// InferForScheduling provides a mock function for the type MockFeature
|
||||
func (_mock *MockFeature) InferForScheduling(podInfo *nodedeclaredfeatures.PodInfo) bool {
|
||||
ret := _mock.Called(podInfo)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for InferForScheduling")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if returnFunc, ok := ret.Get(0).(func(*nodedeclaredfeatures.PodInfo) bool); ok {
|
||||
r0 = returnFunc(podInfo)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockFeature_InferForScheduling_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InferForScheduling'
|
||||
type MockFeature_InferForScheduling_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// InferForScheduling is a helper method to define mock.On call
|
||||
// - podInfo *nodedeclaredfeatures.PodInfo
|
||||
func (_e *MockFeature_Expecter) InferForScheduling(podInfo interface{}) *MockFeature_InferForScheduling_Call {
|
||||
return &MockFeature_InferForScheduling_Call{Call: _e.mock.On("InferForScheduling", podInfo)}
|
||||
}
|
||||
|
||||
func (_c *MockFeature_InferForScheduling_Call) Run(run func(podInfo *nodedeclaredfeatures.PodInfo)) *MockFeature_InferForScheduling_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 *nodedeclaredfeatures.PodInfo
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(*nodedeclaredfeatures.PodInfo)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_InferForScheduling_Call) Return(b bool) *MockFeature_InferForScheduling_Call {
|
||||
_c.Call.Return(b)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_InferForScheduling_Call) RunAndReturn(run func(podInfo *nodedeclaredfeatures.PodInfo) bool) *MockFeature_InferForScheduling_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// InferForUpdate provides a mock function for the type MockFeature
|
||||
func (_mock *MockFeature) InferForUpdate(oldPodInfo *nodedeclaredfeatures.PodInfo, newPodInfo *nodedeclaredfeatures.PodInfo) bool {
|
||||
ret := _mock.Called(oldPodInfo, newPodInfo)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for InferForUpdate")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if returnFunc, ok := ret.Get(0).(func(*nodedeclaredfeatures.PodInfo, *nodedeclaredfeatures.PodInfo) bool); ok {
|
||||
r0 = returnFunc(oldPodInfo, newPodInfo)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockFeature_InferForUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InferForUpdate'
|
||||
type MockFeature_InferForUpdate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// InferForUpdate is a helper method to define mock.On call
|
||||
// - oldPodInfo *nodedeclaredfeatures.PodInfo
|
||||
// - newPodInfo *nodedeclaredfeatures.PodInfo
|
||||
func (_e *MockFeature_Expecter) InferForUpdate(oldPodInfo interface{}, newPodInfo interface{}) *MockFeature_InferForUpdate_Call {
|
||||
return &MockFeature_InferForUpdate_Call{Call: _e.mock.On("InferForUpdate", oldPodInfo, newPodInfo)}
|
||||
}
|
||||
|
||||
func (_c *MockFeature_InferForUpdate_Call) Run(run func(oldPodInfo *nodedeclaredfeatures.PodInfo, newPodInfo *nodedeclaredfeatures.PodInfo)) *MockFeature_InferForUpdate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 *nodedeclaredfeatures.PodInfo
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(*nodedeclaredfeatures.PodInfo)
|
||||
}
|
||||
var arg1 *nodedeclaredfeatures.PodInfo
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*nodedeclaredfeatures.PodInfo)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_InferForUpdate_Call) Return(b bool) *MockFeature_InferForUpdate_Call {
|
||||
_c.Call.Return(b)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_InferForUpdate_Call) RunAndReturn(run func(oldPodInfo *nodedeclaredfeatures.PodInfo, newPodInfo *nodedeclaredfeatures.PodInfo) bool) *MockFeature_InferForUpdate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// MaxVersion provides a mock function for the type MockFeature
|
||||
func (_mock *MockFeature) MaxVersion() *version.Version {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for MaxVersion")
|
||||
}
|
||||
|
||||
var r0 *version.Version
|
||||
if returnFunc, ok := ret.Get(0).(func() *version.Version); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*version.Version)
|
||||
}
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockFeature_MaxVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaxVersion'
|
||||
type MockFeature_MaxVersion_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// MaxVersion is a helper method to define mock.On call
|
||||
func (_e *MockFeature_Expecter) MaxVersion() *MockFeature_MaxVersion_Call {
|
||||
return &MockFeature_MaxVersion_Call{Call: _e.mock.On("MaxVersion")}
|
||||
}
|
||||
|
||||
func (_c *MockFeature_MaxVersion_Call) Run(run func()) *MockFeature_MaxVersion_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_MaxVersion_Call) Return(version1 *version.Version) *MockFeature_MaxVersion_Call {
|
||||
_c.Call.Return(version1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_MaxVersion_Call) RunAndReturn(run func() *version.Version) *MockFeature_MaxVersion_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Name provides a mock function for the type MockFeature
|
||||
func (_mock *MockFeature) Name() string {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Name")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockFeature_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
|
||||
type MockFeature_Name_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Name is a helper method to define mock.On call
|
||||
func (_e *MockFeature_Expecter) Name() *MockFeature_Name_Call {
|
||||
return &MockFeature_Name_Call{Call: _e.mock.On("Name")}
|
||||
}
|
||||
|
||||
func (_c *MockFeature_Name_Call) Run(run func()) *MockFeature_Name_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_Name_Call) Return(s string) *MockFeature_Name_Call {
|
||||
_c.Call.Return(s)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeature_Name_Call) RunAndReturn(run func() string) *MockFeature_Name_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockFeatureGate creates a new instance of MockFeatureGate. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockFeatureGate(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockFeatureGate {
|
||||
mock := &MockFeatureGate{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// MockFeatureGate is an autogenerated mock type for the FeatureGate type
|
||||
type MockFeatureGate struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockFeatureGate_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockFeatureGate) EXPECT() *MockFeatureGate_Expecter {
|
||||
return &MockFeatureGate_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Enabled provides a mock function for the type MockFeatureGate
|
||||
func (_mock *MockFeatureGate) Enabled(key string) bool {
|
||||
ret := _mock.Called(key)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Enabled")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if returnFunc, ok := ret.Get(0).(func(string) bool); ok {
|
||||
r0 = returnFunc(key)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockFeatureGate_Enabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Enabled'
|
||||
type MockFeatureGate_Enabled_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Enabled is a helper method to define mock.On call
|
||||
// - key string
|
||||
func (_e *MockFeatureGate_Expecter) Enabled(key interface{}) *MockFeatureGate_Enabled_Call {
|
||||
return &MockFeatureGate_Enabled_Call{Call: _e.mock.On("Enabled", key)}
|
||||
}
|
||||
|
||||
func (_c *MockFeatureGate_Enabled_Call) Run(run func(key string)) *MockFeatureGate_Enabled_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 string
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(string)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeatureGate_Enabled_Call) Return(b bool) *MockFeatureGate_Enabled_Call {
|
||||
_c.Call.Return(b)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFeatureGate_Enabled_Call) RunAndReturn(run func(key string) bool) *MockFeatureGate_Enabled_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2025 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.
|
||||
*/
|
||||
|
||||
//go:generate mockery
|
||||
package nodedeclaredfeatures
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
)
|
||||
|
||||
// PodInfo is an extensible data structure that wraps the pod object
|
||||
// and can be expanded in the future to include ancillary resources
|
||||
// like ResourceClaims or PVCs.
|
||||
type PodInfo struct {
|
||||
// Spec is the Pod's specification.
|
||||
Spec *v1.PodSpec
|
||||
// Status is the Pod's current status. This field can be nil, for example,
|
||||
// when this struct represents a pod not yet created.
|
||||
// (e.g., during scheduling).
|
||||
Status *v1.PodStatus
|
||||
// Add other ancillary resources here in the future as needed.
|
||||
// Example: ResourceClaims []*v1.ResourceClaim
|
||||
}
|
||||
|
||||
// Feature encapsulates all logic for a given declared feature.
|
||||
type Feature interface {
|
||||
// Name returns the feature's well-known name.
|
||||
Name() string
|
||||
|
||||
// Discover checks if a node provides the feature based on its configuration.
|
||||
Discover(cfg *NodeConfiguration) bool
|
||||
|
||||
// InferForScheduling checks if pod scheduling requires the feature.
|
||||
InferForScheduling(podInfo *PodInfo) bool
|
||||
|
||||
// InferForUpdate checks if a pod update requires the feature.
|
||||
InferForUpdate(oldPodInfo, newPodInfo *PodInfo) bool
|
||||
|
||||
// MaxVersion specifies the upper bound Kubernetes version (inclusive) for this feature's relevance
|
||||
// as a scheduling factor. Should be set based on the feature's GA version
|
||||
// and the cluster's version skew policy. Nil means no upper version bound.
|
||||
// Comparisons use the full semantic versioning scheme.
|
||||
MaxVersion() *version.Version
|
||||
}
|
||||
|
||||
// FeatureGate is an interface that abstracts feature gate checking.
|
||||
type FeatureGate interface {
|
||||
// Enabled returns true if the named feature gate is enabled.
|
||||
Enabled(key string) bool
|
||||
}
|
||||
|
||||
// StaticConfiguration provides a view of a node's static configuration.
|
||||
type StaticConfiguration struct {
|
||||
// Kubelet's CPU Manager policy
|
||||
CPUManagerPolicy string
|
||||
}
|
||||
|
||||
// NodeConfiguration provides a generic view of a node's static configuration.
|
||||
type NodeConfiguration struct {
|
||||
// FeatureGates holds an implementation of the FeatureGate interface.
|
||||
FeatureGates FeatureGate
|
||||
// StaticConfig holds node static configuration.
|
||||
StaticConfig StaticConfiguration
|
||||
// Version holds the current node version. This is used for full semantic version comparisons
|
||||
// with Feature.MaxVersion() to determine if a feature needs to be reported.
|
||||
Version *version.Version
|
||||
}
|
||||
Reference in New Issue
Block a user