From 52b8fa1d050a1db6a6c529b8bebf48c7a93739ff Mon Sep 17 00:00:00 2001 From: Tim Allclair Date: Fri, 9 Jan 2026 12:52:21 -0800 Subject: [PATCH] NodeDeclaredFeatures: Extract ndf types to subpackage to avoid circular dependencies --- .../pod_level_resource_resize.go | 15 +-- .../pod_level_resource_resize_test.go | 34 +++---- .../nodedeclaredfeatures/features/registry.go | 4 +- .../restart_all_containers.go | 15 +-- .../restart_all_containers_test.go | 13 +-- .../nodedeclaredfeatures/framework.go | 11 ++- .../nodedeclaredfeatures/framework_test.go | 69 ++++++------- .../nodedeclaredfeatures/testing/mocks.go | 25 ++--- .../nodedeclaredfeatures/types.go | 67 ++----------- .../nodedeclaredfeatures/types/types.go | 97 +++++++++++++++++++ 10 files changed, 198 insertions(+), 152 deletions(-) create mode 100644 staging/src/k8s.io/component-helpers/nodedeclaredfeatures/types/types.go diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize/pod_level_resource_resize.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize/pod_level_resource_resize.go index f2fe751ef52..6cfa0c78668 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize/pod_level_resource_resize.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize/pod_level_resource_resize.go @@ -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 } diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize/pod_level_resource_resize_test.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize/pod_level_resource_resize_test.go index ddd8b84b4a5..e2c2438275d 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize/pod_level_resource_resize_test.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize/pod_level_resource_resize_test.go @@ -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{}, }, diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/registry.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/registry.go index ae72ab19f48..57256b53dec 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/registry.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/registry.go @@ -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, diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers/restart_all_containers.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers/restart_all_containers.go index 52faed97044..39000819f14 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers/restart_all_containers.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers/restart_all_containers.go @@ -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 } diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers/restart_all_containers_test.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers/restart_all_containers_test.go index c4640b73c1d..7171770987d 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers/restart_all_containers_test.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers/restart_all_containers_test.go @@ -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") } diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/framework.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/framework.go index 9225b0c847a..f2cedd57d56 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/framework.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/framework.go @@ -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") } diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/framework_test.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/framework_test.go index 176d6639bcd..e089f538f04 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/framework_test.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/framework_test.go @@ -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 { diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/testing/mocks.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/testing/mocks.go index 9d05b36be37..9b535cc5b17 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/testing/mocks.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/testing/mocks.go @@ -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 { diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/types.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/types.go index 0b6651f4b0f..d998b5fc0fb 100644 --- a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/types.go +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/types.go @@ -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 diff --git a/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/types/types.go b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/types/types.go new file mode 100644 index 00000000000..502396beb64 --- /dev/null +++ b/staging/src/k8s.io/component-helpers/nodedeclaredfeatures/types/types.go @@ -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{}