mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-17 20:00:07 +00:00
NodeDeclaredFeatures: Extract ndf types to subpackage to avoid circular dependencies
This commit is contained in:
@@ -19,11 +19,11 @@ package inplacepodresize
|
||||
import (
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
// Ensure the feature struct implements the unified Feature interface.
|
||||
var _ nodedeclaredfeatures.Feature = &podLevelResourcesResizeFeature{}
|
||||
var _ types.Feature = &podLevelResourcesResizeFeature{}
|
||||
|
||||
// IPPRPodLevelResourcesFeatureGate is the feature gate for IPPRPodLevelResourcesVerticalScaling.
|
||||
const IPPRPodLevelResourcesFeatureGate = "InPlacePodLevelResourcesVerticalScaling"
|
||||
@@ -37,21 +37,22 @@ func (f *podLevelResourcesResizeFeature) Name() string {
|
||||
return IPPRPodLevelResourcesFeatureGate
|
||||
}
|
||||
|
||||
func (f *podLevelResourcesResizeFeature) Discover(cfg *nodedeclaredfeatures.NodeConfiguration) bool {
|
||||
func (f *podLevelResourcesResizeFeature) Discover(cfg *types.NodeConfiguration) bool {
|
||||
return cfg.FeatureGates.Enabled(IPPRPodLevelResourcesFeatureGate)
|
||||
}
|
||||
|
||||
func (f *podLevelResourcesResizeFeature) Requirements() *nodedeclaredfeatures.FeatureRequirements {
|
||||
return &nodedeclaredfeatures.FeatureRequirements{
|
||||
func (f *podLevelResourcesResizeFeature) Requirements() *types.FeatureRequirements {
|
||||
return &types.FeatureRequirements{
|
||||
EnabledFeatureGates: []string{IPPRPodLevelResourcesFeatureGate},
|
||||
}
|
||||
}
|
||||
func (f *podLevelResourcesResizeFeature) InferForScheduling(podInfo *nodedeclaredfeatures.PodInfo) bool {
|
||||
|
||||
func (f *podLevelResourcesResizeFeature) InferForScheduling(podInfo *types.PodInfo) bool {
|
||||
// This feature is only relevant for pod updates.
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *podLevelResourcesResizeFeature) InferForUpdate(oldPodInfo, newPodInfo *nodedeclaredfeatures.PodInfo) bool {
|
||||
func (f *podLevelResourcesResizeFeature) InferForUpdate(oldPodInfo, newPodInfo *types.PodInfo) bool {
|
||||
if oldPodInfo.Spec.Resources == nil && newPodInfo.Spec.Resources == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures"
|
||||
test "k8s.io/component-helpers/nodedeclaredfeatures/testing"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
func TestPodLevelResourcesResizeFeature_Requirements(t *testing.T) {
|
||||
@@ -56,10 +55,7 @@ func TestPodLevelResourcesResizeFeatureDiscover(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockFG := test.NewMockFeatureGate(t)
|
||||
mockFG.SetEnabled(IPPRPodLevelResourcesFeatureGate, tt.featureGate)
|
||||
|
||||
cfg := &nodedeclaredfeatures.NodeConfiguration{FeatureGates: mockFG}
|
||||
cfg := &types.NodeConfiguration{FeatureGates: types.FeatureGateMap{IPPRPodLevelResourcesFeatureGate: tt.featureGate}}
|
||||
enabled := PodLevelResourcesResizeFeature.Discover(cfg)
|
||||
if want, got := tt.expected, enabled; want != got {
|
||||
t.Fatalf("want=%v,got=%v", want, got)
|
||||
@@ -69,7 +65,7 @@ func TestPodLevelResourcesResizeFeatureDiscover(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPodLevelResourcesResizeFeatureInferForScheduling(t *testing.T) {
|
||||
podInfo := &nodedeclaredfeatures.PodInfo{Spec: &v1.PodSpec{}, Status: &v1.PodStatus{}}
|
||||
podInfo := &types.PodInfo{Spec: &v1.PodSpec{}, Status: &v1.PodStatus{}}
|
||||
if PodLevelResourcesResizeFeature.InferForScheduling(podInfo) {
|
||||
t.Fatalf("InferForScheduling should always be false")
|
||||
}
|
||||
@@ -78,13 +74,13 @@ func TestPodLevelResourcesResizeFeatureInferForScheduling(t *testing.T) {
|
||||
func TestPodLevelResourcesResizeFeatureInferForUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
oldPodInfo *nodedeclaredfeatures.PodInfo
|
||||
newPodInfo *nodedeclaredfeatures.PodInfo
|
||||
oldPodInfo *types.PodInfo
|
||||
newPodInfo *types.PodInfo
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "NoResourcesChanged",
|
||||
oldPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
oldPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{
|
||||
Resources: &v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")},
|
||||
@@ -93,7 +89,7 @@ func TestPodLevelResourcesResizeFeatureInferForUpdate(t *testing.T) {
|
||||
},
|
||||
Status: &v1.PodStatus{},
|
||||
},
|
||||
newPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
newPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{
|
||||
Resources: &v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")},
|
||||
@@ -106,11 +102,11 @@ func TestPodLevelResourcesResizeFeatureInferForUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ResourcesAdded",
|
||||
oldPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
oldPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{},
|
||||
Status: &v1.PodStatus{},
|
||||
},
|
||||
newPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
newPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{
|
||||
Resources: &v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")},
|
||||
@@ -123,7 +119,7 @@ func TestPodLevelResourcesResizeFeatureInferForUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ResourcesRemoved",
|
||||
oldPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
oldPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{
|
||||
Resources: &v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")},
|
||||
@@ -132,7 +128,7 @@ func TestPodLevelResourcesResizeFeatureInferForUpdate(t *testing.T) {
|
||||
},
|
||||
Status: &v1.PodStatus{},
|
||||
},
|
||||
newPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
newPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{},
|
||||
Status: &v1.PodStatus{},
|
||||
},
|
||||
@@ -140,7 +136,7 @@ func TestPodLevelResourcesResizeFeatureInferForUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ResourcesChanged",
|
||||
oldPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
oldPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{
|
||||
Resources: &v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("1")},
|
||||
@@ -149,7 +145,7 @@ func TestPodLevelResourcesResizeFeatureInferForUpdate(t *testing.T) {
|
||||
},
|
||||
Status: &v1.PodStatus{},
|
||||
},
|
||||
newPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
newPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{
|
||||
Resources: &v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("2")},
|
||||
@@ -162,11 +158,11 @@ func TestPodLevelResourcesResizeFeatureInferForUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "NilResources",
|
||||
oldPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
oldPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{},
|
||||
Status: &v1.PodStatus{},
|
||||
},
|
||||
newPodInfo: &nodedeclaredfeatures.PodInfo{
|
||||
newPodInfo: &types.PodInfo{
|
||||
Spec: &v1.PodSpec{},
|
||||
Status: &v1.PodStatus{},
|
||||
},
|
||||
|
||||
@@ -17,16 +17,16 @@ limitations under the License.
|
||||
package features
|
||||
|
||||
import (
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/features/extendwebsocketstokubelet"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
// 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{
|
||||
var AllFeatures = []types.Feature{
|
||||
restartallcontainers.Feature,
|
||||
inplacepodresize.PodLevelResourcesResizeFeature,
|
||||
extendwebsocketstokubelet.Feature,
|
||||
|
||||
@@ -19,11 +19,11 @@ package restartallcontainers
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
// Ensure the feature struct implements the unified Feature interface.
|
||||
var _ nodedeclaredfeatures.Feature = &restartAllContainersFeature{}
|
||||
var _ types.Feature = &restartAllContainersFeature{}
|
||||
|
||||
const (
|
||||
RestartRulesFeatureGate = "ContainerRestartRules"
|
||||
@@ -39,16 +39,17 @@ func (f *restartAllContainersFeature) Name() string {
|
||||
return RestartAllContainersOnContainerExits
|
||||
}
|
||||
|
||||
func (f *restartAllContainersFeature) Discover(cfg *nodedeclaredfeatures.NodeConfiguration) bool {
|
||||
func (f *restartAllContainersFeature) Discover(cfg *types.NodeConfiguration) bool {
|
||||
return cfg.FeatureGates.Enabled(RestartAllContainersOnContainerExits)
|
||||
}
|
||||
|
||||
func (f *restartAllContainersFeature) Requirements() *nodedeclaredfeatures.FeatureRequirements {
|
||||
return &nodedeclaredfeatures.FeatureRequirements{
|
||||
func (f *restartAllContainersFeature) Requirements() *types.FeatureRequirements {
|
||||
return &types.FeatureRequirements{
|
||||
EnabledFeatureGates: []string{RestartAllContainersOnContainerExits},
|
||||
}
|
||||
}
|
||||
func (f *restartAllContainersFeature) InferForScheduling(podInfo *nodedeclaredfeatures.PodInfo) bool {
|
||||
|
||||
func (f *restartAllContainersFeature) InferForScheduling(podInfo *types.PodInfo) bool {
|
||||
for _, c := range podInfo.Spec.Containers {
|
||||
for _, rule := range c.RestartPolicyRules {
|
||||
if rule.Action == v1.ContainerRestartRuleActionRestartAllContainers {
|
||||
@@ -66,7 +67,7 @@ func (f *restartAllContainersFeature) InferForScheduling(podInfo *nodedeclaredfe
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *restartAllContainersFeature) InferForUpdate(oldPodInfo, newPodInfo *nodedeclaredfeatures.PodInfo) bool {
|
||||
func (f *restartAllContainersFeature) InferForUpdate(oldPodInfo, newPodInfo *types.PodInfo) bool {
|
||||
// container.restartPolicy and container.restartPolicyRules are not mutable.
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ import (
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures"
|
||||
test "k8s.io/component-helpers/nodedeclaredfeatures/testing"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
func TestRequirements(t *testing.T) {
|
||||
@@ -56,10 +55,8 @@ func TestDiscover(t *testing.T) {
|
||||
feature := &restartAllContainersFeature{}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockFG := test.NewMockFeatureGate(t)
|
||||
mockFG.SetEnabled(RestartAllContainersOnContainerExits, tc.featureGateEnabled)
|
||||
config := &nodedeclaredfeatures.NodeConfiguration{
|
||||
FeatureGates: mockFG,
|
||||
config := &types.NodeConfiguration{
|
||||
FeatureGates: types.FeatureGateMap{RestartAllContainersOnContainerExits: tc.featureGateEnabled},
|
||||
}
|
||||
enabled := feature.Discover(config)
|
||||
if want, got := tc.expected, enabled; want != got {
|
||||
@@ -118,7 +115,7 @@ func TestInferForScheduling(t *testing.T) {
|
||||
feature := &restartAllContainersFeature{}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
podInfo := &nodedeclaredfeatures.PodInfo{Spec: tc.pod}
|
||||
podInfo := &types.PodInfo{Spec: tc.pod}
|
||||
if want, got := tc.expected, feature.InferForScheduling(podInfo); want != got {
|
||||
t.Fatalf("want=%v,got=%v", want, got)
|
||||
}
|
||||
@@ -128,7 +125,7 @@ func TestInferForScheduling(t *testing.T) {
|
||||
|
||||
func TestInferForUpdate(t *testing.T) {
|
||||
feature := &restartAllContainersFeature{}
|
||||
podInfo := &nodedeclaredfeatures.PodInfo{Spec: &v1.PodSpec{}}
|
||||
podInfo := &types.PodInfo{Spec: &v1.PodSpec{}}
|
||||
if feature.InferForUpdate(nil, podInfo) {
|
||||
t.Fatalf("expect InferForUpdate to be false")
|
||||
}
|
||||
|
||||
@@ -23,12 +23,13 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
// 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
|
||||
registry []types.Feature
|
||||
}
|
||||
|
||||
// FeatureSet is a set of node features.
|
||||
@@ -55,7 +56,7 @@ func (s *FeatureSet) Clone() FeatureSet {
|
||||
}
|
||||
|
||||
// New creates a new instance of the Framework.
|
||||
func New(registry []Feature) (*Framework, error) {
|
||||
func New(registry []types.Feature) (*Framework, error) {
|
||||
if registry == nil {
|
||||
return nil, fmt.Errorf("registry must not be nil")
|
||||
}
|
||||
@@ -66,7 +67,7 @@ func New(registry []Feature) (*Framework, error) {
|
||||
|
||||
// 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 {
|
||||
func (f *Framework) DiscoverNodeFeatures(cfg *types.NodeConfiguration) []string {
|
||||
var enabledFeatures []string
|
||||
for _, f := range f.registry {
|
||||
if f.Discover(cfg) {
|
||||
@@ -81,7 +82,7 @@ func (f *Framework) DiscoverNodeFeatures(cfg *NodeConfiguration) []string {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (f *Framework) InferForPodScheduling(podInfo *types.PodInfo, targetVersion *version.Version) (FeatureSet, error) {
|
||||
if targetVersion == nil {
|
||||
return FeatureSet{}, fmt.Errorf("target version cannot be nil")
|
||||
}
|
||||
@@ -99,7 +100,7 @@ func (f *Framework) InferForPodScheduling(podInfo *PodInfo, targetVersion *versi
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (f *Framework) InferForPodUpdate(oldPodInfo, newPodInfo *types.PodInfo, targetVersion *version.Version) (FeatureSet, error) {
|
||||
if targetVersion == nil {
|
||||
return FeatureSet{}, fmt.Errorf("target version cannot be nil")
|
||||
}
|
||||
|
||||
@@ -27,22 +27,25 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
// 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
|
||||
discover func(cfg *types.NodeConfiguration) bool
|
||||
inferForScheduling func(podInfo *types.PodInfo) bool
|
||||
inferForUpdate func(oldPodInfo, newPodInfo *types.PodInfo) bool
|
||||
maxVersion *version.Version
|
||||
requirements func() *FeatureRequirements
|
||||
}
|
||||
|
||||
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 {
|
||||
func (f *mockFeature) Name() string { return f.name }
|
||||
func (f *mockFeature) Discover(cfg *types.NodeConfiguration) bool { return f.discover(cfg) }
|
||||
func (f *mockFeature) InferForScheduling(podInfo *types.PodInfo) bool {
|
||||
return f.inferForScheduling(podInfo)
|
||||
}
|
||||
func (f *mockFeature) InferForUpdate(oldPodInfo, newPodInfo *types.PodInfo) bool {
|
||||
return f.inferForUpdate(oldPodInfo, newPodInfo)
|
||||
}
|
||||
func (f *mockFeature) MaxVersion() *version.Version { return f.maxVersion }
|
||||
@@ -69,7 +72,7 @@ func TestNewFramework(t *testing.T) {
|
||||
t.Fatalf("NewFramework should return an error with a nil registry")
|
||||
}
|
||||
|
||||
_, err = New([]Feature{})
|
||||
_, err = New([]types.Feature{})
|
||||
if err != nil {
|
||||
t.Fatalf("NewFramework should not return an error with an empty registry")
|
||||
}
|
||||
@@ -77,10 +80,10 @@ func TestNewFramework(t *testing.T) {
|
||||
|
||||
func TestDiscoverNodeFeatures(t *testing.T) {
|
||||
featureMaxVersion := version.MustParse("1.38.0")
|
||||
registry := []Feature{
|
||||
registry := []types.Feature{
|
||||
&mockFeature{
|
||||
name: "FeatureA",
|
||||
discover: func(cfg *NodeConfiguration) bool {
|
||||
discover: func(cfg *types.NodeConfiguration) bool {
|
||||
return cfg.FeatureGates.Enabled("feature-a")
|
||||
},
|
||||
requirements: func() *FeatureRequirements {
|
||||
@@ -92,7 +95,7 @@ func TestDiscoverNodeFeatures(t *testing.T) {
|
||||
},
|
||||
&mockFeature{
|
||||
name: "FeatureB",
|
||||
discover: func(cfg *NodeConfiguration) bool {
|
||||
discover: func(cfg *types.NodeConfiguration) bool {
|
||||
return cfg.FeatureGates.Enabled("feature-b")
|
||||
},
|
||||
requirements: func() *FeatureRequirements {
|
||||
@@ -108,19 +111,19 @@ func TestDiscoverNodeFeatures(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config *NodeConfiguration
|
||||
config *types.NodeConfiguration
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "Feature Enabled",
|
||||
config: &NodeConfiguration{
|
||||
config: &types.NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{string("feature-a"): true}),
|
||||
},
|
||||
expected: []string{"FeatureA"},
|
||||
},
|
||||
{
|
||||
name: "multiple features enabled",
|
||||
config: &NodeConfiguration{
|
||||
config: &types.NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{
|
||||
string("feature-a"): true,
|
||||
string("feature-b"): true,
|
||||
@@ -130,7 +133,7 @@ func TestDiscoverNodeFeatures(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no features enabled",
|
||||
config: &NodeConfiguration{
|
||||
config: &types.NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{
|
||||
string("feature-a"): false,
|
||||
string("feature-b"): false,
|
||||
@@ -140,7 +143,7 @@ func TestDiscoverNodeFeatures(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "feature past max version",
|
||||
config: &NodeConfiguration{
|
||||
config: &types.NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{string("feature-a"): true}),
|
||||
Version: featureMaxVersion.AddMinor(1),
|
||||
},
|
||||
@@ -148,7 +151,7 @@ func TestDiscoverNodeFeatures(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "feature past max version - pre-release version",
|
||||
config: &NodeConfiguration{
|
||||
config: &types.NodeConfiguration{
|
||||
FeatureGates: newMockFeatureGate(map[string]bool{string("feature-a"): true}),
|
||||
Version: version.MustParse("1.39.0-alpha.2.39+049eafd34dfbd2"),
|
||||
},
|
||||
@@ -167,7 +170,7 @@ func TestDiscoverNodeFeatures(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInferForPodScheduling(t *testing.T) {
|
||||
inferPodlevelResources := func(p *PodInfo) bool {
|
||||
inferPodlevelResources := func(p *types.PodInfo) bool {
|
||||
return p.Spec.Resources != nil
|
||||
}
|
||||
podWithPodLevelResources := &v1.Pod{
|
||||
@@ -194,7 +197,7 @@ func TestInferForPodScheduling(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
registry []Feature
|
||||
registry []types.Feature
|
||||
newPod *v1.Pod
|
||||
targetVersion *version.Version
|
||||
expectedReqs FeatureSet
|
||||
@@ -203,7 +206,7 @@ func TestInferForPodScheduling(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "pod with feature, inferred during scheduling",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
@@ -217,7 +220,7 @@ func TestInferForPodScheduling(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "pod without feature",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
@@ -231,7 +234,7 @@ func TestInferForPodScheduling(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "feature universally available, not inferred during create",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
@@ -246,7 +249,7 @@ func TestInferForPodScheduling(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "pre-release target version",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
@@ -261,7 +264,7 @@ func TestInferForPodScheduling(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "target version nil",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "PodLevelResources",
|
||||
inferForScheduling: inferPodlevelResources,
|
||||
@@ -279,7 +282,7 @@ func TestInferForPodScheduling(t *testing.T) {
|
||||
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)
|
||||
reqs, err := framework.InferForPodScheduling(&types.PodInfo{Spec: &tc.newPod.Spec, Status: &tc.newPod.Status}, tc.targetVersion)
|
||||
|
||||
if tc.expectErr {
|
||||
if err == nil {
|
||||
@@ -299,7 +302,7 @@ func TestInferForPodScheduling(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInferForPodUpdate(t *testing.T) {
|
||||
inferResize := func(oldPodInfo, newPodInfo *PodInfo) bool {
|
||||
inferResize := func(oldPodInfo, newPodInfo *types.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) {
|
||||
@@ -336,7 +339,7 @@ func TestInferForPodUpdate(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
registry []Feature
|
||||
registry []types.Feature
|
||||
oldPod *v1.Pod
|
||||
newPod *v1.Pod
|
||||
targetVersion *version.Version
|
||||
@@ -346,7 +349,7 @@ func TestInferForPodUpdate(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "pod update requires feature",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
@@ -361,7 +364,7 @@ func TestInferForPodUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "pod update requires feature with pre-release version",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
@@ -376,7 +379,7 @@ func TestInferForPodUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "pod update does not require feature",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
@@ -391,7 +394,7 @@ func TestInferForPodUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "feature universally available, not inferred during update",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
@@ -407,7 +410,7 @@ func TestInferForPodUpdate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "target version nil",
|
||||
registry: []Feature{
|
||||
registry: []types.Feature{
|
||||
&mockFeature{
|
||||
name: "InPlacePodResize",
|
||||
inferForUpdate: inferResize,
|
||||
@@ -426,7 +429,7 @@ func TestInferForPodUpdate(t *testing.T) {
|
||||
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)
|
||||
reqs, err := framework.InferForPodUpdate(&types.PodInfo{Spec: &tc.oldPod.Spec, Status: &tc.oldPod.Status}, &types.PodInfo{Spec: &tc.newPod.Spec, Status: &tc.newPod.Status}, tc.targetVersion)
|
||||
|
||||
if tc.expectErr {
|
||||
if err == nil {
|
||||
|
||||
@@ -21,9 +21,10 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
var _ = nodedeclaredfeatures.FeatureGate((*MockFeatureGate)(nil))
|
||||
var _ = types.FeatureGate((*MockFeatureGate)(nil))
|
||||
|
||||
type MockFeatureGate struct {
|
||||
t *testing.T
|
||||
@@ -48,16 +49,16 @@ func NewMockFeatureGate(t *testing.T) *MockFeatureGate {
|
||||
}
|
||||
}
|
||||
|
||||
var _ = nodedeclaredfeatures.Feature((*MockFeature)(nil))
|
||||
var _ = types.Feature((*MockFeature)(nil))
|
||||
|
||||
type MockFeature struct {
|
||||
t *testing.T
|
||||
name *string
|
||||
discover func(cfg *nodedeclaredfeatures.NodeConfiguration) bool
|
||||
inferForScheduling func(podInfo *nodedeclaredfeatures.PodInfo) bool
|
||||
inferForUpdate func(oldPodInfo, newPodInfo *nodedeclaredfeatures.PodInfo) bool
|
||||
discover func(cfg *types.NodeConfiguration) bool
|
||||
inferForScheduling func(podInfo *types.PodInfo) bool
|
||||
inferForUpdate func(oldPodInfo, newPodInfo *types.PodInfo) bool
|
||||
maxVersion **version.Version
|
||||
requirements **nodedeclaredfeatures.FeatureRequirements
|
||||
requirements **types.FeatureRequirements
|
||||
}
|
||||
|
||||
func (m *MockFeature) Name() string {
|
||||
@@ -70,34 +71,34 @@ func (m *MockFeature) Name() string {
|
||||
func (m *MockFeature) SetName(name string) {
|
||||
m.name = &name
|
||||
}
|
||||
func (m *MockFeature) Discover(cfg *nodedeclaredfeatures.NodeConfiguration) bool {
|
||||
func (m *MockFeature) Discover(cfg *types.NodeConfiguration) bool {
|
||||
if m.discover == nil {
|
||||
m.t.Errorf("unexpected call to Discover")
|
||||
return false
|
||||
}
|
||||
return m.discover(cfg)
|
||||
}
|
||||
func (m *MockFeature) SetDiscover(discover func(cfg *nodedeclaredfeatures.NodeConfiguration) bool) {
|
||||
func (m *MockFeature) SetDiscover(discover func(cfg *types.NodeConfiguration) bool) {
|
||||
m.discover = discover
|
||||
}
|
||||
func (m *MockFeature) InferForScheduling(podInfo *nodedeclaredfeatures.PodInfo) bool {
|
||||
func (m *MockFeature) InferForScheduling(podInfo *types.PodInfo) bool {
|
||||
if m.inferForScheduling == nil {
|
||||
m.t.Errorf("unexpected call to InferForScheduling")
|
||||
return false
|
||||
}
|
||||
return m.inferForScheduling(podInfo)
|
||||
}
|
||||
func (m *MockFeature) SetInferForScheduling(inferForScheduling func(podInfo *nodedeclaredfeatures.PodInfo) bool) {
|
||||
func (m *MockFeature) SetInferForScheduling(inferForScheduling func(podInfo *types.PodInfo) bool) {
|
||||
m.inferForScheduling = inferForScheduling
|
||||
}
|
||||
func (m *MockFeature) InferForUpdate(oldPodInfo, newPodInfo *nodedeclaredfeatures.PodInfo) bool {
|
||||
func (m *MockFeature) InferForUpdate(oldPodInfo, newPodInfo *types.PodInfo) bool {
|
||||
if m.inferForUpdate == nil {
|
||||
m.t.Errorf("unexpected call to InferForUpdate")
|
||||
return false
|
||||
}
|
||||
return m.inferForUpdate(oldPodInfo, newPodInfo)
|
||||
}
|
||||
func (m *MockFeature) SetInferForUpdate(inferForUpdate func(oldPodInfo, newPodInfo *nodedeclaredfeatures.PodInfo) bool) {
|
||||
func (m *MockFeature) SetInferForUpdate(inferForUpdate func(oldPodInfo, newPodInfo *types.PodInfo) bool) {
|
||||
m.inferForUpdate = inferForUpdate
|
||||
}
|
||||
func (m *MockFeature) MaxVersion() *version.Version {
|
||||
|
||||
@@ -17,65 +17,14 @@ limitations under the License.
|
||||
package nodedeclaredfeatures
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/component-helpers/nodedeclaredfeatures/types"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
// The following type aliases are provided as convenience to framework users.
|
||||
|
||||
// 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
|
||||
|
||||
// Requirements returns the feature's feature gate and static config dependencies.
|
||||
Requirements() *FeatureRequirements
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// FeatureRequirements lists the potential dependencies of a feature.
|
||||
type FeatureRequirements struct {
|
||||
// EnabledFeatureGates lists feature gate strings that the feature depends on.
|
||||
EnabledFeatureGates []string
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// NodeConfiguration provides a generic view of a node's static configuration.
|
||||
type NodeConfiguration struct {
|
||||
// FeatureGates holds an implementation of the FeatureGate interface.
|
||||
FeatureGates FeatureGate
|
||||
// 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
|
||||
}
|
||||
type PodInfo = types.PodInfo
|
||||
type Feature = types.Feature
|
||||
type FeatureRequirements = types.FeatureRequirements
|
||||
type FeatureGate = types.FeatureGate
|
||||
type StaticConfiguration = types.StaticConfiguration
|
||||
type NodeConfiguration = types.NodeConfiguration
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
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 types
|
||||
|
||||
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
|
||||
|
||||
// Requirements returns the feature's feature gate and static config dependencies.
|
||||
Requirements() *FeatureRequirements
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// FeatureRequirements lists the potential dependencies of a feature.
|
||||
type FeatureRequirements struct {
|
||||
// EnabledFeatureGates lists feature gate strings that the feature depends on.
|
||||
EnabledFeatureGates []string
|
||||
}
|
||||
|
||||
// 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 required for feature discovery.
|
||||
type StaticConfiguration struct {
|
||||
// Add configuration fields here as required by registered features.
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// FeatureGateMap is provided as a convenience implementation of FeatureGate
|
||||
type FeatureGateMap map[string]bool
|
||||
|
||||
func (m FeatureGateMap) Enabled(key string) bool {
|
||||
return m[key]
|
||||
}
|
||||
|
||||
var _ FeatureGate = FeatureGateMap{}
|
||||
Reference in New Issue
Block a user