Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Field status.hostIPs added for Pod #109616

Merged
merged 12 commits into from
Jul 15, 2023
28 changes: 24 additions & 4 deletions api/openapi-spec/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 29 additions & 4 deletions api/openapi-spec/v3/api__v1_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2673,6 +2673,16 @@
},
"type": "object"
},
"io.k8s.api.core.v1.HostIP": {
"description": "HostIP represents a single IP address allocated to the host.",
"properties": {
"ip": {
"description": "IP is the IP address assigned to the host",
"type": "string"
}
},
"type": "object"
},
"io.k8s.api.core.v1.HostPathVolumeSource": {
"description": "Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.",
"properties": {
Expand Down Expand Up @@ -4875,10 +4885,10 @@
"type": "object"
},
"io.k8s.api.core.v1.PodIP": {
"description": "IP address information for entries in the (plural) PodIPs field. Each entry includes:\n\n\tIP: An IP address allocated to the pod. Routable at least within the cluster.",
"description": "PodIP represents a single IP address allocated to the pod.",
"properties": {
"ip": {
"description": "ip is an IP address (IPv4 or IPv6) assigned to the pod",
"description": "IP is the IP address assigned to the pod",
"type": "string"
}
},
Expand Down Expand Up @@ -5444,9 +5454,24 @@
"type": "array"
},
"hostIP": {
"description": "IP address of the host to which the pod is assigned. Empty if not yet scheduled.",
"description": "hostIP holds the IP address of the host to which the pod is assigned. Empty if the pod has not started yet. A pod can be assigned to a node that has a problem in kubelet which in turns mean that HostIP will not be updated even if there is a node is assigned to pod",
"type": "string"
},
"hostIPs": {
"description": "hostIPs holds the IP addresses allocated to the host. If this field is specified, the first entry must match the hostIP field. This list is empty if the pod has not started yet. A pod can be assigned to a node that has a problem in kubelet which in turns means that HostIPs will not be updated even if there is a node is assigned to this pod.",
"items": {
"allOf": [
{
"$ref": "#/components/schemas/io.k8s.api.core.v1.HostIP"
}
],
"default": {}
},
"type": "array",
"x-kubernetes-list-type": "atomic",
"x-kubernetes-patch-merge-key": "ip",
"x-kubernetes-patch-strategy": "merge"
},
"initContainerStatuses": {
"description": "The list has one entry per init container in the manifest. The most recent successful init container will have ready = true, the most recently started container will have startTime set. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status",
"items": {
Expand All @@ -5472,7 +5497,7 @@
"type": "string"
},
"podIP": {
"description": "IP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.",
"description": "podIP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.",
"type": "string"
},
"podIPs": {
Expand Down
66 changes: 66 additions & 0 deletions pkg/api/pod/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
// default pod validation options based on feature gate
opts := apivalidation.PodValidationOptions{
AllowInvalidPodDeletionCost: !utilfeature.DefaultFeatureGate.Enabled(features.PodDeletionCost),
// Allow pod spec to use status.hostIPs in downward API if feature is enabled
AllowHostIPsField: utilfeature.DefaultFeatureGate.Enabled(features.PodHostIPs),
// Do not allow pod spec to use non-integer multiple of huge page unit size default
AllowIndivisibleHugePagesValues: false,
AllowInvalidLabelValueInSelector: false,
Expand All @@ -366,6 +368,9 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
}

if oldPodSpec != nil {
// if old spec has status.hostIPs downwardAPI set, we must allow it
opts.AllowHostIPsField = opts.AllowHostIPsField || hasUsedDownwardAPIFieldPathWithPodSpec(oldPodSpec, "status.hostIPs")

// if old spec used non-integer multiple of huge page unit size, we must allow it
opts.AllowIndivisibleHugePagesValues = usesIndivisibleHugePagesValues(oldPodSpec)

Expand All @@ -382,6 +387,55 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
return opts
}

func hasUsedDownwardAPIFieldPathWithPodSpec(podSpec *api.PodSpec, fieldPath string) bool {
if podSpec == nil {
return false
}
for _, vol := range podSpec.Volumes {
if hasUsedDownwardAPIFieldPathWithVolume(&vol, fieldPath) {
return true
}
}
for _, c := range podSpec.InitContainers {
if hasUsedDownwardAPIFieldPathWithContainer(&c, fieldPath) {
return true
}
}
for _, c := range podSpec.Containers {
if hasUsedDownwardAPIFieldPathWithContainer(&c, fieldPath) {
return true
}
}
return false
}

func hasUsedDownwardAPIFieldPathWithVolume(volume *api.Volume, fieldPath string) bool {
if volume == nil || volume.DownwardAPI == nil {
return false
}
for _, file := range volume.DownwardAPI.Items {
thockin marked this conversation as resolved.
Show resolved Hide resolved
if file.FieldRef != nil &&
file.FieldRef.FieldPath == fieldPath {
return true
}
}
return false
}

func hasUsedDownwardAPIFieldPathWithContainer(container *api.Container, fieldPath string) bool {
if container == nil {
return false
}
for _, env := range container.Env {
if env.ValueFrom != nil &&
env.ValueFrom.FieldRef != nil &&
env.ValueFrom.FieldRef.FieldPath == fieldPath {
return true
}
}
return false
}

// GetValidationOptionsFromPodTemplate will return pod validation options for specified template.
func GetValidationOptionsFromPodTemplate(podTemplate, oldPodTemplate *api.PodTemplateSpec) apivalidation.PodValidationOptions {
var newPodSpec, oldPodSpec *api.PodSpec
Expand Down Expand Up @@ -544,6 +598,18 @@ func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec
if !utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) && !dynamicResourceAllocationInUse(oldPodSpec) {
podStatus.ResourceClaimStatuses = nil
}

// drop HostIPs to empty (disable PodHostIPs).
if !utilfeature.DefaultFeatureGate.Enabled(features.PodHostIPs) && !hostIPsInUse(oldPodStatus) {
podStatus.HostIPs = nil
}
}

func hostIPsInUse(podStatus *api.PodStatus) bool {
if podStatus == nil {
return false
}
return len(podStatus.HostIPs) > 0
}

// dropDisabledDynamicResourceAllocationFields removes pod claim references from
Expand Down
106 changes: 106 additions & 0 deletions pkg/api/pod/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,112 @@ func TestDropDisabledTopologySpreadConstraintsFields(t *testing.T) {
}
}

func TestDropDisabledPodStatusFields(t *testing.T) {
podWithHostIPs := func() *api.PodStatus {
return &api.PodStatus{
HostIPs: makeHostIPs("10.0.0.1", "fd00:10::1"),
}
}

podWithoutHostIPs := func() *api.PodStatus {
return &api.PodStatus{
HostIPs: nil,
}
}

tests := []struct {
name string
podStatus *api.PodStatus
oldPodStatus *api.PodStatus
wantPodStatus *api.PodStatus
featureEnabled bool
}{
{
name: "gate off, old=without, new=without",
oldPodStatus: podWithoutHostIPs(),
podStatus: podWithoutHostIPs(),
featureEnabled: false,

wantPodStatus: podWithoutHostIPs(),
},
{
name: "gate off, old=without, new=with",
oldPodStatus: podWithoutHostIPs(),
podStatus: podWithHostIPs(),
featureEnabled: false,

wantPodStatus: podWithoutHostIPs(),
},
{
name: "gate off, old=with, new=without",
oldPodStatus: podWithHostIPs(),
podStatus: podWithoutHostIPs(),
featureEnabled: false,

wantPodStatus: podWithoutHostIPs(),
},
{
name: "gate off, old=with, new=with",
oldPodStatus: podWithHostIPs(),
podStatus: podWithHostIPs(),
featureEnabled: false,

wantPodStatus: podWithHostIPs(),
},
{
name: "gate on, old=without, new=without",
oldPodStatus: podWithoutHostIPs(),
podStatus: podWithoutHostIPs(),
featureEnabled: true,

wantPodStatus: podWithoutHostIPs(),
},
{
name: "gate on, old=without, new=with",
oldPodStatus: podWithoutHostIPs(),
podStatus: podWithHostIPs(),
featureEnabled: true,

wantPodStatus: podWithHostIPs(),
},
{
name: "gate on, old=with, new=without",
oldPodStatus: podWithHostIPs(),
podStatus: podWithoutHostIPs(),
featureEnabled: true,

wantPodStatus: podWithoutHostIPs(),
},
{
name: "gate on, old=with, new=with",
oldPodStatus: podWithHostIPs(),
podStatus: podWithHostIPs(),
featureEnabled: true,

wantPodStatus: podWithHostIPs(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, tt.featureEnabled)()

dropDisabledPodStatusFields(tt.podStatus, tt.oldPodStatus, &api.PodSpec{}, &api.PodSpec{})

if !reflect.DeepEqual(tt.podStatus, tt.wantPodStatus) {
t.Errorf("dropDisabledStatusFields() = %v, want %v", tt.podStatus, tt.wantPodStatus)
}
})
}
}

func makeHostIPs(ips ...string) []api.HostIP {
ret := []api.HostIP{}
for _, ip := range ips {
ret = append(ret, api.HostIP{IP: ip})
}
return ret
}

func TestDropNodeInclusionPolicyFields(t *testing.T) {
ignore := api.NodeInclusionPolicyIgnore
honor := api.NodeInclusionPolicyHonor
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/core/fuzzer/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
s.EnableServiceLinks = &enableServiceLinks
}
},
func(s *core.PodStatus, c fuzz.Continue) {
thockin marked this conversation as resolved.
Show resolved Hide resolved
c.Fuzz(&s)
s.HostIPs = []core.HostIP{{IP: s.HostIP}}
},
func(j *core.PodPhase, c fuzz.Continue) {
statuses := []core.PodPhase{core.PodPending, core.PodRunning, core.PodFailed, core.PodUnknown}
*j = statuses[c.Rand.Intn(len(statuses))]
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/core/pods/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func ConvertDownwardAPIFieldLabel(version, label, value string) (string, string,
"spec.schedulerName",
"status.phase",
"status.hostIP",
"status.hostIPs",
"status.podIP",
"status.podIPs":
return label, value, nil
Expand Down
14 changes: 14 additions & 0 deletions pkg/apis/core/pods/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,20 @@ func TestConvertDownwardAPIFieldLabel(t *testing.T) {
expectedLabel: "status.podIPs",
expectedValue: "10.244.0.6",
},
{
version: "v1",
label: "status.hostIPs",
value: "10.244.0.6,fd00::6",
expectedLabel: "status.hostIPs",
expectedValue: "10.244.0.6,fd00::6",
},
{
version: "v1",
label: "status.hostIPs",
value: "10.244.0.6",
expectedLabel: "status.hostIPs",
expectedValue: "10.244.0.6",
},
}
for _, tc := range testCases {
label, value, err := ConvertDownwardAPIFieldLabel(tc.version, tc.label, tc.value)
Expand Down
Loading