Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/features/kube_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -2408,7 +2408,7 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature

TranslateStreamCloseWebsocketRequests: {},

UserNamespacesHostNetworkSupport: {UserNamespacesSupport},
UserNamespacesHostNetworkSupport: {UserNamespacesSupport, NodeDeclaredFeatures},

UserNamespacesSupport: {},

Expand Down
5 changes: 3 additions & 2 deletions pkg/kubelet/container/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,15 +652,16 @@ func (c *RuntimeCondition) String() string {

// RuntimeFeatures contains the set of features implemented by the runtime
type RuntimeFeatures struct {
SupplementalGroupsPolicy bool
SupplementalGroupsPolicy bool
UserNamespacesHostNetwork bool
}

// String formats the runtime condition into a human readable string.
func (f *RuntimeFeatures) String() string {
if f == nil {
return "nil"
}
return fmt.Sprintf("SupplementalGroupsPolicy: %v", f.SupplementalGroupsPolicy)
return fmt.Sprintf("SupplementalGroupsPolicy: %v UserNamespacesHostNetwork: %v", f.SupplementalGroupsPolicy, f.UserNamespacesHostNetwork)
}

// Pods represents the list of pods
Expand Down
18 changes: 10 additions & 8 deletions pkg/kubelet/container/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,11 +603,11 @@ func TestRuntimeStatusString(t *testing.T) {
{Name: "handler1", SupportsRecursiveReadOnlyMounts: true, SupportsUserNamespaces: false},
{Name: "handler2", SupportsRecursiveReadOnlyMounts: false, SupportsUserNamespaces: true},
},
Features: &RuntimeFeatures{SupplementalGroupsPolicy: true},
Features: &RuntimeFeatures{SupplementalGroupsPolicy: true, UserNamespacesHostNetwork: true},
}

result := status.String()
expected := "Runtime Conditions: RuntimeReady=true reason:ready message:runtime is ready, NetworkReady=false reason:not ready message:network is not ready; Handlers: Name=handler1 SupportsRecursiveReadOnlyMounts: true SupportsUserNamespaces: false, Name=handler2 SupportsRecursiveReadOnlyMounts: false SupportsUserNamespaces: true, Features: SupplementalGroupsPolicy: true"
expected := "Runtime Conditions: RuntimeReady=true reason:ready message:runtime is ready, NetworkReady=false reason:not ready message:network is not ready; Handlers: Name=handler1 SupportsRecursiveReadOnlyMounts: true SupportsUserNamespaces: false, Name=handler2 SupportsRecursiveReadOnlyMounts: false SupportsUserNamespaces: true, Features: SupplementalGroupsPolicy: true UserNamespacesHostNetwork: true"
assert.Equal(t, expected, result, "String()")
}

Expand Down Expand Up @@ -688,18 +688,20 @@ func TestRuntimeFeaturesString(t *testing.T) {
expected string
}{
{
name: "features with SupplementalGroupsPolicy true",
name: "features with both flags true",
features: &RuntimeFeatures{
SupplementalGroupsPolicy: true,
SupplementalGroupsPolicy: true,
UserNamespacesHostNetwork: true,
},
expected: "SupplementalGroupsPolicy: true",
expected: "SupplementalGroupsPolicy: true UserNamespacesHostNetwork: true",
},
{
name: "features with SupplementalGroupsPolicy false",
name: "features with both flags false",
features: &RuntimeFeatures{
SupplementalGroupsPolicy: false,
SupplementalGroupsPolicy: false,
UserNamespacesHostNetwork: false,
},
expected: "SupplementalGroupsPolicy: false",
expected: "SupplementalGroupsPolicy: false UserNamespacesHostNetwork: false",
},
{
name: "nil features",
Expand Down
12 changes: 9 additions & 3 deletions pkg/kubelet/kubelet_node_declared_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ func (kl *Kubelet) discoverNodeDeclaredFeatures() []string {
CPUManagerPolicy: kl.containerManager.GetNodeConfig().CPUManagerPolicy,
}

runtimeFeatures := nodedeclaredfeatures.RuntimeFeatures{}
if features := kl.runtimeState.runtimeFeatures(); features != nil {
runtimeFeatures.UserNamespacesHostNetwork = features.UserNamespacesHostNetwork
}

adaptedFG := FeatureGateAdapter{FeatureGate: utilfeature.DefaultFeatureGate}
cfg := &nodedeclaredfeatures.NodeConfiguration{
FeatureGates: adaptedFG,
StaticConfig: staticConfig,
Version: kl.version,
FeatureGates: adaptedFG,
StaticConfig: staticConfig,
Version: kl.version,
RuntimeFeatures: runtimeFeatures,
}
return kl.nodeDeclaredFeaturesFramework.DiscoverNodeFeatures(cfg)
}
3 changes: 2 additions & 1 deletion pkg/kubelet/kuberuntime/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus, handlers []*runtimeap
var retFeatures *kubecontainer.RuntimeFeatures
if features != nil {
retFeatures = &kubecontainer.RuntimeFeatures{
SupplementalGroupsPolicy: features.SupplementalGroupsPolicy,
SupplementalGroupsPolicy: features.SupplementalGroupsPolicy,
UserNamespacesHostNetwork: features.UserNamespacesHostNetwork,
}
}
return &kubecontainer.RuntimeStatus{Conditions: conditions, Handlers: retHandlers, Features: retFeatures}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"k8s.io/component-helpers/nodedeclaredfeatures"
"k8s.io/component-helpers/nodedeclaredfeatures/features/inplacepodresize"
"k8s.io/component-helpers/nodedeclaredfeatures/features/restartallcontainers"
"k8s.io/component-helpers/nodedeclaredfeatures/features/usernamespaceshostnetwork"
)

// AllFeatures is the central registry for all declared features.
Expand All @@ -29,4 +30,5 @@ var AllFeatures = []nodedeclaredfeatures.Feature{
restartallcontainers.Feature,
inplacepodresize.GuaranteedQoSPodCPUResizeFeature,
inplacepodresize.PodLevelResourcesResizeFeature,
usernamespaceshostnetwork.Feature,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
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.
*/

package usernamespaceshostnetwork

import (
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/component-helpers/nodedeclaredfeatures"
)

// Ensure the feature struct implements the unified Feature interface.
var _ nodedeclaredfeatures.Feature = &userNamespacesHostNetworkFeature{}

const (
// UserNamespacesHostNetworkSupportFeatureGate is the feature gate name.
UserNamespacesHostNetworkSupportFeatureGate = "UserNamespacesHostNetworkSupport"
// UserNamespacesHostNetworkSupport is the declared feature name.
UserNamespacesHostNetworkSupport = "UserNamespacesHostNetworkSupport"
)

// Feature is the implementation of the `UserNamespacesHostNetworkSupport` feature.
var Feature = &userNamespacesHostNetworkFeature{}

type userNamespacesHostNetworkFeature struct{}

func (f *userNamespacesHostNetworkFeature) Name() string {
return UserNamespacesHostNetworkSupport
}

func (f *userNamespacesHostNetworkFeature) Discover(cfg *nodedeclaredfeatures.NodeConfiguration) bool {
// This feature requires both the feature gate to be enabled AND
// runtime-level support for user namespaces with host network.
if !cfg.FeatureGates.Enabled(UserNamespacesHostNetworkSupportFeatureGate) {
return false
}

return cfg.RuntimeFeatures.UserNamespacesHostNetwork
}

func (f *userNamespacesHostNetworkFeature) InferForScheduling(podInfo *nodedeclaredfeatures.PodInfo) bool {
// A pod needs this feature if it uses both host network AND user namespaces.
if podInfo.Spec.HostNetwork && podInfo.Spec.HostUsers != nil && !*podInfo.Spec.HostUsers {
return true
}
return false
}

func (f *userNamespacesHostNetworkFeature) InferForUpdate(oldPodInfo, newPodInfo *nodedeclaredfeatures.PodInfo) bool {
// HostNetwork and HostUsers fields are immutable, so no update inference is needed.
return false
}

func (f *userNamespacesHostNetworkFeature) MaxVersion() *version.Version {
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
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.
*/

package usernamespaceshostnetwork

import (
"testing"

"k8s.io/component-helpers/nodedeclaredfeatures"
test "k8s.io/component-helpers/nodedeclaredfeatures/testing"

"github.com/stretchr/testify/assert"
)

func TestDiscoverFeature(t *testing.T) {
tests := []struct {
name string
featureGate bool
runtimeFeatures nodedeclaredfeatures.RuntimeFeatures
expected bool
}{
{
name: "feature gate disabled",
featureGate: false,
runtimeFeatures: nodedeclaredfeatures.RuntimeFeatures{
UserNamespacesHostNetwork: true,
},
expected: false,
},
{
name: "feature gate enabled but no runtime support",
featureGate: true,
runtimeFeatures: nodedeclaredfeatures.RuntimeFeatures{
UserNamespacesHostNetwork: false,
},
expected: false,
},
{
name: "feature gate enabled and runtime supports it",
featureGate: true,
runtimeFeatures: nodedeclaredfeatures.RuntimeFeatures{
UserNamespacesHostNetwork: true,
},
expected: true,
},
{
name: "runtime support is on",
featureGate: true,
runtimeFeatures: nodedeclaredfeatures.RuntimeFeatures{
UserNamespacesHostNetwork: true,
},
expected: true,
},
{
name: "runtime support is off",
featureGate: true,
runtimeFeatures: nodedeclaredfeatures.RuntimeFeatures{
UserNamespacesHostNetwork: false,
},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockFG := test.NewMockFeatureGate(t)
mockFG.EXPECT().Enabled(UserNamespacesHostNetworkSupportFeatureGate).Return(tt.featureGate)

cfg := &nodedeclaredfeatures.NodeConfiguration{
FeatureGates: mockFG,
RuntimeFeatures: tt.runtimeFeatures,
}

result := Feature.Discover(cfg)
assert.Equal(t, tt.expected, result)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ type StaticConfiguration struct {
CPUManagerPolicy string
}

// RuntimeFeatures provides information about CRI runtime-level capabilities.
type RuntimeFeatures struct {
// UserNamespacesHostNetwork indicates if the runtime supports user namespaces with host network.
UserNamespacesHostNetwork bool
}

// NodeConfiguration provides a generic view of a node's static configuration.
type NodeConfiguration struct {
// FeatureGates holds an implementation of the FeatureGate interface.
Expand All @@ -78,4 +84,6 @@ type NodeConfiguration struct {
// 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
// RuntimeFeatures holds runtime-level capabilities discovered from CRI.
RuntimeFeatures RuntimeFeatures
}
Loading