mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 09:52:49 +00:00
Field status.hostIPs
added for Pod (#101566)
* Add FeatureGate PodHostIPs * Add HostIPs field and update PodIPs field * Types conversion * Add dropDisabledStatusFields * Add HostIPs for kubelet * Add fuzzer for PodStatus * Add status.hostIPs in ConvertDownwardAPIFieldLabel * Add status.hostIPs in validEnvDownwardAPIFieldPathExpressions * Downward API support for status.hostIPs * Add DownwardAPI validation for status.hostIPs * Add e2e to check that hostIPs works * Add e2e to check that Downward API works * Regenerate
This commit is contained in:
parent
05b59e7717
commit
61b3c028ba
28
api/openapi-spec/swagger.json
generated
28
api/openapi-spec/swagger.json
generated
@ -6712,6 +6712,16 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.HostIP": {
|
||||
"description": "HostIP address information for entries in the (plural) HostIPs field.",
|
||||
"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": {
|
||||
@ -8378,10 +8388,10 @@
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodIP": {
|
||||
"description": "IP address information for entries in the (plural) PodIPs field. Each entry includes:\n IP: An IP address allocated to the pod. Routable at least within the cluster.",
|
||||
"description": "PodIP address information for entries in the (plural) PodIPs field.",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
@ -8751,9 +8761,19 @@
|
||||
"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 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": {
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.HostIP"
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-type": "set",
|
||||
"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": {
|
||||
@ -8781,7 +8801,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": {
|
||||
|
@ -2221,6 +2221,16 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.HostIP": {
|
||||
"description": "HostIP address information for entries in the (plural) HostIPs field.",
|
||||
"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": {
|
||||
@ -4008,10 +4018,10 @@
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.PodIP": {
|
||||
"description": "IP address information for entries in the (plural) PodIPs field. Each entry includes:\n IP: An IP address allocated to the pod. Routable at least within the cluster.",
|
||||
"description": "PodIP address information for entries in the (plural) PodIPs field.",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
@ -4401,9 +4411,20 @@
|
||||
"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 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": {
|
||||
"$ref": "#/components/schemas/io.k8s.api.core.v1.HostIP",
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-type": "set",
|
||||
"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": {
|
||||
@ -4432,7 +4453,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": {
|
||||
|
@ -434,6 +434,8 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
||||
AllowExpandedDNSConfig: utilfeature.DefaultFeatureGate.Enabled(features.ExpandedDNSConfig) || haveSameExpandedDNSConfig(podSpec, oldPodSpec),
|
||||
// Allow pod spec to use OS field
|
||||
AllowOSField: utilfeature.DefaultFeatureGate.Enabled(features.IdentifyPodOS),
|
||||
// Allow pod spec to use status.hostIPs in downward API if feature is enabled
|
||||
AllowHostIPsField: utilfeature.DefaultFeatureGate.Enabled(features.PodHostIPs),
|
||||
// The default sysctl value does not contain a forward slash, and in 1.24 we intend to relax this to be true by default
|
||||
AllowSysctlRegexContainSlash: false,
|
||||
}
|
||||
@ -454,6 +456,9 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
||||
// if old spec has OS field set, we must allow it
|
||||
opts.AllowOSField = opts.AllowOSField || oldPodSpec.OS != 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)
|
||||
|
||||
@ -470,6 +475,57 @@ 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 {
|
||||
return false
|
||||
}
|
||||
if volume.DownwardAPI != nil {
|
||||
for _, file := range volume.DownwardAPI.Items {
|
||||
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
|
||||
@ -512,19 +568,39 @@ func DropDisabledTemplateFields(podTemplate, oldPodTemplate *api.PodTemplateSpec
|
||||
func DropDisabledPodFields(pod, oldPod *api.Pod) {
|
||||
var (
|
||||
podSpec *api.PodSpec
|
||||
podStatus *api.PodStatus
|
||||
podAnnotations map[string]string
|
||||
oldPodSpec *api.PodSpec
|
||||
oldPodStatus *api.PodStatus
|
||||
oldPodAnnotations map[string]string
|
||||
)
|
||||
if pod != nil {
|
||||
podSpec = &pod.Spec
|
||||
podAnnotations = pod.Annotations
|
||||
podStatus = &pod.Status
|
||||
}
|
||||
if oldPod != nil {
|
||||
oldPodSpec = &oldPod.Spec
|
||||
oldPodAnnotations = oldPod.Annotations
|
||||
oldPodStatus = &oldPod.Status
|
||||
}
|
||||
dropDisabledFields(podSpec, podAnnotations, oldPodSpec, oldPodAnnotations)
|
||||
dropDisabledStatusFields(podStatus, oldPodStatus)
|
||||
}
|
||||
|
||||
// dropDisabledStatusFields removes disabled fields from the pod status
|
||||
func dropDisabledStatusFields(podStatus *api.PodStatus, oldPodStatus *api.PodStatus) {
|
||||
// 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
|
||||
}
|
||||
|
||||
// dropDisabledFields removes disabled fields from the pod metadata and spec.
|
||||
|
@ -1768,3 +1768,73 @@ func TestDropOSField(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropDisabledStatusFields(t *testing.T) {
|
||||
podWithHostIP := 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
|
||||
}{
|
||||
{
|
||||
podStatus: podWithHostIP(),
|
||||
oldPodStatus: podWithHostIP(),
|
||||
featureEnabled: false,
|
||||
|
||||
wantPodStatus: podWithHostIP(),
|
||||
},
|
||||
{
|
||||
podStatus: podWithoutHostIPs(),
|
||||
oldPodStatus: podWithHostIP(),
|
||||
featureEnabled: true,
|
||||
|
||||
wantPodStatus: podWithoutHostIPs(),
|
||||
},
|
||||
{
|
||||
podStatus: podWithoutHostIPs(),
|
||||
oldPodStatus: podWithoutHostIPs(),
|
||||
featureEnabled: false,
|
||||
|
||||
wantPodStatus: podWithoutHostIPs(),
|
||||
},
|
||||
{
|
||||
podStatus: podWithHostIP(),
|
||||
oldPodStatus: podWithoutHostIPs(),
|
||||
featureEnabled: true,
|
||||
|
||||
wantPodStatus: podWithHostIP(),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, tt.featureEnabled)()
|
||||
|
||||
dropDisabledStatusFields(tt.podStatus, tt.oldPodStatus)
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -91,6 +91,10 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
||||
s.EnableServiceLinks = &enableServiceLinks
|
||||
}
|
||||
},
|
||||
func(s *core.PodStatus, c fuzz.Continue) {
|
||||
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))]
|
||||
|
@ -88,6 +88,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
|
||||
|
@ -3212,10 +3212,14 @@ type PodDNSConfigOption struct {
|
||||
}
|
||||
|
||||
// PodIP represents the IP address of a pod.
|
||||
// IP address information. Each entry includes:
|
||||
// IP: An IP address allocated to the pod. Routable at least within
|
||||
// the cluster.
|
||||
type PodIP struct {
|
||||
// ip is an IP address assigned to the pod
|
||||
IP string
|
||||
}
|
||||
|
||||
// HostIP represents the IP address of a host.
|
||||
type HostIP struct {
|
||||
// ip is an IP address assigned to the host
|
||||
IP string
|
||||
}
|
||||
|
||||
@ -3356,9 +3360,22 @@ type PodStatus struct {
|
||||
// give the resources on this node to a higher priority pod that is created after preemption.
|
||||
// +optional
|
||||
NominatedNodeName string
|
||||
|
||||
// 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 node is assigned to pod
|
||||
// +optional
|
||||
HostIP string
|
||||
|
||||
// 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.
|
||||
// match the hostIP field. This list is empty if no IPs have been allocated yet.
|
||||
// +optional
|
||||
HostIPs []HostIP
|
||||
|
||||
// PodIPs holds all of the known IP addresses allocated to the pod. Pods may be assigned AT MOST
|
||||
// one value for each of IPv4 and IPv6.
|
||||
// +optional
|
||||
|
@ -270,6 +270,17 @@ func Convert_v1_PodStatus_To_core_PodStatus(in *v1.PodStatus, out *core.PodStatu
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// If both fields (v1.HostIPs and v1.HostIP) are provided and differ, then HostIP is authoritative for compatibility with older kubelets
|
||||
if (len(in.HostIP) > 0 && len(in.HostIPs) > 0) && (in.HostIP != in.HostIPs[0].IP) {
|
||||
out.HostIPs = []core.HostIP{{IP: in.HostIP}}
|
||||
}
|
||||
// at the this point, autoConvert copied v1.HostIPs -> core.HostIPs
|
||||
// if v1.HostIPs was empty but v1.HostIP is not, then set core.HostIPs[0] with v1.HostIP
|
||||
if len(in.HostIP) > 0 && len(in.HostIPs) == 0 {
|
||||
out.HostIPs = []core.HostIP{{IP: in.HostIP}}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -283,6 +294,11 @@ func Convert_core_PodStatus_To_v1_PodStatus(in *core.PodStatus, out *v1.PodStatu
|
||||
if len(in.PodIPs) > 0 {
|
||||
out.PodIP = in.PodIPs[0].IP
|
||||
}
|
||||
|
||||
// at the this point autoConvert copied core.HostIPs -> v1.HostIPs
|
||||
if len(in.HostIPs) > 0 {
|
||||
out.HostIP = in.HostIPs[0].IP
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
32
pkg/apis/core/v1/zz_generated.conversion.go
generated
32
pkg/apis/core/v1/zz_generated.conversion.go
generated
@ -712,6 +712,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.HostIP)(nil), (*core.HostIP)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_HostIP_To_core_HostIP(a.(*v1.HostIP), b.(*core.HostIP), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*core.HostIP)(nil), (*v1.HostIP)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_core_HostIP_To_v1_HostIP(a.(*core.HostIP), b.(*v1.HostIP), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.HostPathVolumeSource)(nil), (*core.HostPathVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_HostPathVolumeSource_To_core_HostPathVolumeSource(a.(*v1.HostPathVolumeSource), b.(*core.HostPathVolumeSource), scope)
|
||||
}); err != nil {
|
||||
@ -4005,6 +4015,26 @@ func Convert_core_HostAlias_To_v1_HostAlias(in *core.HostAlias, out *v1.HostAlia
|
||||
return autoConvert_core_HostAlias_To_v1_HostAlias(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_HostIP_To_core_HostIP(in *v1.HostIP, out *core.HostIP, s conversion.Scope) error {
|
||||
out.IP = in.IP
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_HostIP_To_core_HostIP is an autogenerated conversion function.
|
||||
func Convert_v1_HostIP_To_core_HostIP(in *v1.HostIP, out *core.HostIP, s conversion.Scope) error {
|
||||
return autoConvert_v1_HostIP_To_core_HostIP(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_core_HostIP_To_v1_HostIP(in *core.HostIP, out *v1.HostIP, s conversion.Scope) error {
|
||||
out.IP = in.IP
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_core_HostIP_To_v1_HostIP is an autogenerated conversion function.
|
||||
func Convert_core_HostIP_To_v1_HostIP(in *core.HostIP, out *v1.HostIP, s conversion.Scope) error {
|
||||
return autoConvert_core_HostIP_To_v1_HostIP(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_HostPathVolumeSource_To_core_HostPathVolumeSource(in *v1.HostPathVolumeSource, out *core.HostPathVolumeSource, s conversion.Scope) error {
|
||||
out.Path = in.Path
|
||||
out.Type = (*core.HostPathType)(unsafe.Pointer(in.Type))
|
||||
@ -6247,6 +6277,7 @@ func autoConvert_v1_PodStatus_To_core_PodStatus(in *v1.PodStatus, out *core.PodS
|
||||
out.Reason = in.Reason
|
||||
out.NominatedNodeName = in.NominatedNodeName
|
||||
out.HostIP = in.HostIP
|
||||
out.HostIPs = *(*[]core.HostIP)(unsafe.Pointer(&in.HostIPs))
|
||||
// WARNING: in.PodIP requires manual conversion: does not exist in peer-type
|
||||
out.PodIPs = *(*[]core.PodIP)(unsafe.Pointer(&in.PodIPs))
|
||||
out.StartTime = (*metav1.Time)(unsafe.Pointer(in.StartTime))
|
||||
@ -6264,6 +6295,7 @@ func autoConvert_core_PodStatus_To_v1_PodStatus(in *core.PodStatus, out *v1.PodS
|
||||
out.Reason = in.Reason
|
||||
out.NominatedNodeName = in.NominatedNodeName
|
||||
out.HostIP = in.HostIP
|
||||
out.HostIPs = *(*[]v1.HostIP)(unsafe.Pointer(&in.HostIPs))
|
||||
out.PodIPs = *(*[]v1.PodIP)(unsafe.Pointer(&in.PodIPs))
|
||||
out.StartTime = (*metav1.Time)(unsafe.Pointer(in.StartTime))
|
||||
out.QOSClass = v1.PodQOSClass(in.QOSClass)
|
||||
|
@ -1044,6 +1044,7 @@ func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *fi
|
||||
if file.ResourceFieldRef != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously"))
|
||||
}
|
||||
allErrs = append(allErrs, validateDownwardAPIHostIPs(file.FieldRef, fldPath.Child("fieldRef"), opts)...)
|
||||
} else if file.ResourceFieldRef != nil {
|
||||
localValidContainerResourceFieldPathPrefixes := validContainerResourceFieldPathPrefixes
|
||||
if opts.AllowDownwardAPIHugePages {
|
||||
@ -2329,8 +2330,10 @@ var validEnvDownwardAPIFieldPathExpressions = sets.NewString(
|
||||
"spec.nodeName",
|
||||
"spec.serviceAccountName",
|
||||
"status.hostIP",
|
||||
"status.hostIPs",
|
||||
"status.podIP",
|
||||
"status.podIPs")
|
||||
"status.podIPs",
|
||||
)
|
||||
|
||||
var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage")
|
||||
|
||||
@ -2353,6 +2356,7 @@ func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path, opts PodValida
|
||||
if ev.ValueFrom.FieldRef != nil {
|
||||
numSources++
|
||||
allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validEnvDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
||||
allErrs = append(allErrs, validateDownwardAPIHostIPs(ev.ValueFrom.FieldRef, fldPath.Child("fieldRef"), opts)...)
|
||||
}
|
||||
if ev.ValueFrom.ResourceFieldRef != nil {
|
||||
numSources++
|
||||
@ -2423,6 +2427,16 @@ func validateObjectFieldSelector(fs *core.ObjectFieldSelector, expressions *sets
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateDownwardAPIHostIPs(fieldSel *core.ObjectFieldSelector, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if !opts.AllowHostIPsField {
|
||||
if fieldSel.FieldPath == "status.hostIPs" {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "status.hostIPs", "not allowed when feature gate 'PodHostIPs' is not enabled"))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateContainerResourceFieldSelector(fs *core.ResourceFieldSelector, expressions *sets.String, prefixes *sets.String, fldPath *field.Path, volume bool) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@ -3415,6 +3429,8 @@ type PodValidationOptions struct {
|
||||
AllowExpandedDNSConfig bool
|
||||
// Allow OSField to be set in the pod spec
|
||||
AllowOSField bool
|
||||
// Allow pod spec to use status.hostIPs in downward API
|
||||
AllowHostIPsField bool
|
||||
// Allow sysctl name to contain a slash
|
||||
AllowSysctlRegexContainSlash bool
|
||||
}
|
||||
|
@ -5688,7 +5688,7 @@ func TestValidateEnv(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}},
|
||||
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`,
|
||||
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
|
||||
},
|
||||
{
|
||||
name: "metadata.annotations without subscript",
|
||||
@ -5701,7 +5701,7 @@ func TestValidateEnv(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}},
|
||||
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`,
|
||||
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
|
||||
},
|
||||
{
|
||||
name: "metadata.annotations with invalid key",
|
||||
@ -5740,7 +5740,7 @@ func TestValidateEnv(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}},
|
||||
expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`,
|
||||
expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.hostIPs", "status.podIP", "status.podIPs"`,
|
||||
},
|
||||
}
|
||||
for _, tc := range errorCases {
|
||||
@ -20455,3 +20455,50 @@ func TestValidateAppArmorProfileFormat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDownwardAPIHostIPs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectError bool
|
||||
featureEnabled bool
|
||||
fieldSel *core.ObjectFieldSelector
|
||||
}{
|
||||
{
|
||||
name: "has no hostIPs field, featuregate enabled",
|
||||
expectError: false,
|
||||
featureEnabled: true,
|
||||
fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIP"},
|
||||
},
|
||||
{
|
||||
name: "has hostIPs field, featuregate enabled",
|
||||
expectError: false,
|
||||
featureEnabled: true,
|
||||
fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIPs"},
|
||||
},
|
||||
{
|
||||
name: "has no hostIPs field, featuregate disabled",
|
||||
expectError: false,
|
||||
featureEnabled: false,
|
||||
fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIP"},
|
||||
},
|
||||
{
|
||||
name: "has hostIPs field, featuregate disabled",
|
||||
expectError: true,
|
||||
featureEnabled: false,
|
||||
fieldSel: &core.ObjectFieldSelector{FieldPath: "status.hostIPs"},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, testCase.featureEnabled)()
|
||||
|
||||
errs := validateDownwardAPIHostIPs(testCase.fieldSel, field.NewPath("fieldSel"), PodValidationOptions{AllowHostIPsField: testCase.featureEnabled})
|
||||
if testCase.expectError && len(errs) == 0 {
|
||||
t.Errorf("Unexpected success")
|
||||
}
|
||||
if !testCase.expectError && len(errs) != 0 {
|
||||
t.Errorf("Unexpected error(s): %v", errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
21
pkg/apis/core/zz_generated.deepcopy.go
generated
21
pkg/apis/core/zz_generated.deepcopy.go
generated
@ -1802,6 +1802,22 @@ func (in *HostAlias) DeepCopy() *HostAlias {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostIP) DeepCopyInto(out *HostIP) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostIP.
|
||||
func (in *HostIP) DeepCopy() *HostIP {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostIP)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostPathVolumeSource) DeepCopyInto(out *HostPathVolumeSource) {
|
||||
*out = *in
|
||||
@ -3974,6 +3990,11 @@ func (in *PodStatus) DeepCopyInto(out *PodStatus) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.HostIPs != nil {
|
||||
in, out := &in.HostIPs, &out.HostIPs
|
||||
*out = make([]HostIP, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.PodIPs != nil {
|
||||
in, out := &in.PodIPs, &out.PodIPs
|
||||
*out = make([]PodIP, len(*in))
|
||||
|
@ -411,6 +411,13 @@ const (
|
||||
// Enables ipv6 dual stack
|
||||
IPv6DualStack featuregate.Feature = "IPv6DualStack"
|
||||
|
||||
// owner: @wzshiming
|
||||
// kep: http://kep.k8s.io/2681
|
||||
// alpha: v1.24
|
||||
//
|
||||
// Adds pod.status.hostIPs and downward API
|
||||
PodHostIPs featuregate.Feature = "PodHostIPs"
|
||||
|
||||
// owner: @robscott @freehan
|
||||
// kep: http://kep.k8s.io/752
|
||||
// alpha: v1.16
|
||||
@ -933,6 +940,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
NonPreemptingPriority: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
|
||||
PodOverhead: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.26
|
||||
IPv6DualStack: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
|
||||
PodHostIPs: {Default: false, PreRelease: featuregate.Alpha},
|
||||
EndpointSlice: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
|
||||
EndpointSliceProxying: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
|
||||
EndpointSliceTerminatingCondition: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
52
pkg/generated/openapi/zz_generated.openapi.go
generated
52
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -369,6 +369,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"k8s.io/api/core/v1.HTTPGetAction": schema_k8sio_api_core_v1_HTTPGetAction(ref),
|
||||
"k8s.io/api/core/v1.HTTPHeader": schema_k8sio_api_core_v1_HTTPHeader(ref),
|
||||
"k8s.io/api/core/v1.HostAlias": schema_k8sio_api_core_v1_HostAlias(ref),
|
||||
"k8s.io/api/core/v1.HostIP": schema_k8sio_api_core_v1_HostIP(ref),
|
||||
"k8s.io/api/core/v1.HostPathVolumeSource": schema_k8sio_api_core_v1_HostPathVolumeSource(ref),
|
||||
"k8s.io/api/core/v1.ISCSIPersistentVolumeSource": schema_k8sio_api_core_v1_ISCSIPersistentVolumeSource(ref),
|
||||
"k8s.io/api/core/v1.ISCSIVolumeSource": schema_k8sio_api_core_v1_ISCSIVolumeSource(ref),
|
||||
@ -18047,6 +18048,26 @@ func schema_k8sio_api_core_v1_HostAlias(ref common.ReferenceCallback) common.Ope
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_core_v1_HostIP(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "HostIP address information for entries in the (plural) HostIPs field.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"ip": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "IP is the IP address assigned to the host",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_core_v1_HostPathVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@ -21377,12 +21398,12 @@ func schema_k8sio_api_core_v1_PodIP(ref common.ReferenceCallback) common.OpenAPI
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "IP address information for entries in the (plural) PodIPs field. Each entry includes:\n IP: An IP address allocated to the pod. Routable at least within the cluster.",
|
||||
Description: "PodIP address information for entries in the (plural) PodIPs field.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"ip": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
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{"string"},
|
||||
Format: "",
|
||||
},
|
||||
@ -22226,14 +22247,35 @@ func schema_k8sio_api_core_v1_PodStatus(ref common.ReferenceCallback) common.Ope
|
||||
},
|
||||
"hostIP": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
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 node is assigned to pod",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"hostIPs": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "set",
|
||||
"x-kubernetes-patch-merge-key": "ip",
|
||||
"x-kubernetes-patch-strategy": "merge",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
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.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/core/v1.HostIP"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"podIP": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
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{"string"},
|
||||
Format: "",
|
||||
},
|
||||
@ -22317,7 +22359,7 @@ func schema_k8sio_api_core_v1_PodStatus(ref common.ReferenceCallback) common.Ope
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/core/v1.ContainerStatus", "k8s.io/api/core/v1.PodCondition", "k8s.io/api/core/v1.PodIP", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
"k8s.io/api/core/v1.ContainerStatus", "k8s.io/api/core/v1.HostIP", "k8s.io/api/core/v1.PodCondition", "k8s.io/api/core/v1.PodIP", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -826,6 +826,19 @@ func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *v1.ObjectFieldSelector, pod
|
||||
return "", err
|
||||
}
|
||||
return hostIPs[0].String(), nil
|
||||
case "status.hostIPs":
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.PodHostIPs) {
|
||||
return "", nil
|
||||
}
|
||||
hostIPs, err := kl.getHostIPsAnyWay()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ips := make([]string, 0, len(hostIPs))
|
||||
for _, ip := range hostIPs {
|
||||
ips = append(ips, ip.String())
|
||||
}
|
||||
return strings.Join(ips, ","), nil
|
||||
case "status.podIP":
|
||||
return podIP, nil
|
||||
case "status.podIPs":
|
||||
@ -1517,6 +1530,13 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po
|
||||
klog.V(4).InfoS("Cannot get host IPs", "err", err)
|
||||
} else {
|
||||
s.HostIP = hostIPs[0].String()
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.PodHostIPs) {
|
||||
ips := make([]v1.HostIP, 0, len(hostIPs))
|
||||
for _, hostIP := range hostIPs {
|
||||
ips = append(ips, v1.HostIP{IP: hostIP.String()})
|
||||
}
|
||||
s.HostIPs = ips
|
||||
}
|
||||
// HostNetwork Pods inherit the node IPs as PodIPs. They are immutable once set,
|
||||
// other than that if the node becomes dual-stack, we add the secondary IP.
|
||||
if kubecontainer.IsHostNetworkPod(pod) {
|
||||
|
@ -38,8 +38,10 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/record"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
netutils "k8s.io/utils/net"
|
||||
|
||||
// TODO: remove this import if
|
||||
@ -47,6 +49,7 @@ import (
|
||||
// to "v1"?
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cri/streaming/portforward"
|
||||
@ -2470,6 +2473,10 @@ func TestConvertToAPIContainerStatuses(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_generateAPIPodStatus(t *testing.T) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.PodHostIPs) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodHostIPs, true)()
|
||||
}
|
||||
|
||||
desiredState := v1.PodSpec{
|
||||
NodeName: "machine",
|
||||
Containers: []v1.Container{
|
||||
@ -2510,6 +2517,7 @@ func Test_generateAPIPodStatus(t *testing.T) {
|
||||
expected: v1.PodStatus{
|
||||
Phase: v1.PodRunning,
|
||||
HostIP: "127.0.0.1",
|
||||
HostIPs: []v1.HostIP{{IP: "127.0.0.1"}},
|
||||
QOSClass: v1.PodQOSBestEffort,
|
||||
Conditions: []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue},
|
||||
@ -2544,6 +2552,7 @@ func Test_generateAPIPodStatus(t *testing.T) {
|
||||
expected: v1.PodStatus{
|
||||
Phase: v1.PodRunning,
|
||||
HostIP: "127.0.0.1",
|
||||
HostIPs: []v1.HostIP{{IP: "127.0.0.1"}},
|
||||
QOSClass: v1.PodQOSBestEffort,
|
||||
Conditions: []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue},
|
||||
@ -2579,6 +2588,7 @@ func Test_generateAPIPodStatus(t *testing.T) {
|
||||
expected: v1.PodStatus{
|
||||
Phase: v1.PodSucceeded,
|
||||
HostIP: "127.0.0.1",
|
||||
HostIPs: []v1.HostIP{{IP: "127.0.0.1"}},
|
||||
QOSClass: v1.PodQOSBestEffort,
|
||||
Conditions: []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"},
|
||||
@ -2618,6 +2628,7 @@ func Test_generateAPIPodStatus(t *testing.T) {
|
||||
expected: v1.PodStatus{
|
||||
Phase: v1.PodSucceeded,
|
||||
HostIP: "127.0.0.1",
|
||||
HostIPs: []v1.HostIP{{IP: "127.0.0.1"}},
|
||||
QOSClass: v1.PodQOSBestEffort,
|
||||
Conditions: []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"},
|
||||
@ -2666,6 +2677,7 @@ func Test_generateAPIPodStatus(t *testing.T) {
|
||||
expected: v1.PodStatus{
|
||||
Phase: v1.PodSucceeded,
|
||||
HostIP: "127.0.0.1",
|
||||
HostIPs: []v1.HostIP{{IP: "127.0.0.1"}},
|
||||
QOSClass: v1.PodQOSBestEffort,
|
||||
Conditions: []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue, Reason: "PodCompleted"},
|
||||
@ -2703,6 +2715,7 @@ func Test_generateAPIPodStatus(t *testing.T) {
|
||||
expected: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
HostIP: "127.0.0.1",
|
||||
HostIPs: []v1.HostIP{{IP: "127.0.0.1"}},
|
||||
QOSClass: v1.PodQOSBestEffort,
|
||||
Conditions: []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue},
|
||||
@ -2752,6 +2765,7 @@ func Test_generateAPIPodStatus(t *testing.T) {
|
||||
Reason: "Test",
|
||||
Message: "test",
|
||||
HostIP: "127.0.0.1",
|
||||
HostIPs: []v1.HostIP{{IP: "127.0.0.1"}},
|
||||
QOSClass: v1.PodQOSBestEffort,
|
||||
Conditions: []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue},
|
||||
@ -2805,6 +2819,7 @@ func Test_generateAPIPodStatus(t *testing.T) {
|
||||
expected: v1.PodStatus{
|
||||
Phase: v1.PodRunning,
|
||||
HostIP: "127.0.0.1",
|
||||
HostIPs: []v1.HostIP{{IP: "127.0.0.1"}},
|
||||
QOSClass: v1.PodQOSBestEffort,
|
||||
Conditions: []v1.PodCondition{
|
||||
{Type: v1.PodInitialized, Status: v1.ConditionTrue},
|
||||
|
2293
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
2293
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -1767,6 +1767,12 @@ message HostAlias {
|
||||
repeated string hostnames = 2;
|
||||
}
|
||||
|
||||
// HostIP address information for entries in the (plural) HostIPs field.
|
||||
message HostIP {
|
||||
// IP is the IP address assigned to the host
|
||||
optional string ip = 1;
|
||||
}
|
||||
|
||||
// Represents a host path mapped into a pod.
|
||||
// Host path volumes do not support ownership management or SELinux relabeling.
|
||||
message HostPathVolumeSource {
|
||||
@ -3230,11 +3236,9 @@ message PodExecOptions {
|
||||
repeated string command = 6;
|
||||
}
|
||||
|
||||
// IP address information for entries in the (plural) PodIPs field.
|
||||
// Each entry includes:
|
||||
// IP: An IP address allocated to the pod. Routable at least within the cluster.
|
||||
// PodIP address information for entries in the (plural) PodIPs field.
|
||||
message PodIP {
|
||||
// ip is an IP address (IPv4 or IPv6) assigned to the pod
|
||||
// IP is the IP address assigned to the pod
|
||||
optional string ip = 1;
|
||||
}
|
||||
|
||||
@ -3774,11 +3778,24 @@ message PodStatus {
|
||||
// +optional
|
||||
optional string nominatedNodeName = 11;
|
||||
|
||||
// IP address of the host to which the pod is assigned. Empty if not yet scheduled.
|
||||
// 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 node is assigned to pod
|
||||
// +optional
|
||||
optional string hostIP = 5;
|
||||
|
||||
// IP address allocated to the pod. Routable at least within the cluster.
|
||||
// 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.
|
||||
// +optional
|
||||
// +patchStrategy=merge
|
||||
// +patchMergeKey=ip
|
||||
// +listType=set
|
||||
repeated HostIP hostIPs = 14;
|
||||
|
||||
// podIP address allocated to the pod. Routable at least within the cluster.
|
||||
// Empty if not yet allocated.
|
||||
// +optional
|
||||
optional string podIP = 6;
|
||||
|
@ -3605,11 +3605,15 @@ type PodDNSConfigOption struct {
|
||||
Value *string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
|
||||
}
|
||||
|
||||
// IP address information for entries in the (plural) PodIPs field.
|
||||
// Each entry includes:
|
||||
// IP: An IP address allocated to the pod. Routable at least within the cluster.
|
||||
// PodIP address information for entries in the (plural) PodIPs field.
|
||||
type PodIP struct {
|
||||
// ip is an IP address (IPv4 or IPv6) assigned to the pod
|
||||
// IP is the IP address assigned to the pod
|
||||
IP string `json:"ip,omitempty" protobuf:"bytes,1,opt,name=ip"`
|
||||
}
|
||||
|
||||
// HostIP address information for entries in the (plural) HostIPs field.
|
||||
type HostIP struct {
|
||||
// IP is the IP address assigned to the host
|
||||
IP string `json:"ip,omitempty" protobuf:"bytes,1,opt,name=ip"`
|
||||
}
|
||||
|
||||
@ -3830,10 +3834,24 @@ type PodStatus struct {
|
||||
// +optional
|
||||
NominatedNodeName string `json:"nominatedNodeName,omitempty" protobuf:"bytes,11,opt,name=nominatedNodeName"`
|
||||
|
||||
// IP address of the host to which the pod is assigned. Empty if not yet scheduled.
|
||||
// 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 node is assigned to pod
|
||||
// +optional
|
||||
HostIP string `json:"hostIP,omitempty" protobuf:"bytes,5,opt,name=hostIP"`
|
||||
// IP address allocated to the pod. Routable at least within the cluster.
|
||||
|
||||
// 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.
|
||||
// +optional
|
||||
// +patchStrategy=merge
|
||||
// +patchMergeKey=ip
|
||||
// +listType=set
|
||||
HostIPs []HostIP `json:"hostIPs,omitempty" protobuf:"bytes,14,rep,name=hostIPs" patchStrategy:"merge" patchMergeKey:"ip"`
|
||||
|
||||
// podIP address allocated to the pod. Routable at least within the cluster.
|
||||
// Empty if not yet allocated.
|
||||
// +optional
|
||||
PodIP string `json:"podIP,omitempty" protobuf:"bytes,6,opt,name=podIP"`
|
||||
|
@ -825,6 +825,15 @@ func (HostAlias) SwaggerDoc() map[string]string {
|
||||
return map_HostAlias
|
||||
}
|
||||
|
||||
var map_HostIP = map[string]string{
|
||||
"": "HostIP address information for entries in the (plural) HostIPs field.",
|
||||
"ip": "IP is the IP address assigned to the host",
|
||||
}
|
||||
|
||||
func (HostIP) SwaggerDoc() map[string]string {
|
||||
return map_HostIP
|
||||
}
|
||||
|
||||
var map_HostPathVolumeSource = map[string]string{
|
||||
"": "Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.",
|
||||
"path": "path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath",
|
||||
@ -1534,8 +1543,8 @@ func (PodExecOptions) SwaggerDoc() map[string]string {
|
||||
}
|
||||
|
||||
var map_PodIP = map[string]string{
|
||||
"": "IP address information for entries in the (plural) PodIPs field. Each entry includes:\n IP: An IP address allocated to the pod. Routable at least within the cluster.",
|
||||
"ip": "ip is an IP address (IPv4 or IPv6) assigned to the pod",
|
||||
"": "PodIP address information for entries in the (plural) PodIPs field.",
|
||||
"ip": "IP is the IP address assigned to the pod",
|
||||
}
|
||||
|
||||
func (PodIP) SwaggerDoc() map[string]string {
|
||||
@ -1683,8 +1692,9 @@ var map_PodStatus = map[string]string{
|
||||
"message": "A human readable message indicating details about why the pod is in this condition.",
|
||||
"reason": "A brief CamelCase message indicating details about why the pod is in this state. e.g. 'Evicted'",
|
||||
"nominatedNodeName": "nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be scheduled right away as preemption victims receive their graceful termination periods. This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to give the resources on this node to a higher priority pod that is created after preemption. As a result, this field may be different than PodSpec.nodeName when the pod is scheduled.",
|
||||
"hostIP": "IP address of the host to which the pod is assigned. Empty if not yet scheduled.",
|
||||
"podIP": "IP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.",
|
||||
"hostIP": "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 node is assigned to pod",
|
||||
"hostIPs": "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.",
|
||||
"podIP": "podIP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.",
|
||||
"podIPs": "podIPs holds the IP addresses allocated to the pod. If this field is specified, the 0th entry must match the podIP field. Pods may be allocated at most 1 value for each of IPv4 and IPv6. This list is empty if no IPs have been allocated yet.",
|
||||
"startTime": "RFC 3339 date and time at which the object was acknowledged by the Kubelet. This is before the Kubelet pulled the container image(s) for the pod.",
|
||||
"initContainerStatuses": "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",
|
||||
|
@ -1802,6 +1802,22 @@ func (in *HostAlias) DeepCopy() *HostAlias {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostIP) DeepCopyInto(out *HostIP) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostIP.
|
||||
func (in *HostIP) DeepCopy() *HostIP {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HostIP)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HostPathVolumeSource) DeepCopyInto(out *HostPathVolumeSource) {
|
||||
*out = *in
|
||||
@ -3972,6 +3988,11 @@ func (in *PodStatus) DeepCopyInto(out *PodStatus) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.HostIPs != nil {
|
||||
in, out := &in.HostIPs, &out.HostIPs
|
||||
*out = make([]HostIP, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.PodIPs != nil {
|
||||
in, out := &in.PodIPs, &out.PodIPs
|
||||
*out = make([]PodIP, len(*in))
|
||||
|
@ -1582,6 +1582,11 @@
|
||||
"reason": "reasonValue",
|
||||
"nominatedNodeName": "nominatedNodeNameValue",
|
||||
"hostIP": "hostIPValue",
|
||||
"hostIPs": [
|
||||
{
|
||||
"ip": "ipValue"
|
||||
}
|
||||
],
|
||||
"podIP": "podIPValue",
|
||||
"podIPs": [
|
||||
{
|
||||
|
BIN
staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb
vendored
BIN
staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb
vendored
Binary file not shown.
@ -1159,6 +1159,8 @@ status:
|
||||
message: messageValue
|
||||
reason: reasonValue
|
||||
hostIP: hostIPValue
|
||||
hostIPs:
|
||||
- ip: ipValue
|
||||
initContainerStatuses:
|
||||
- containerID: containerIDValue
|
||||
image: imageValue
|
||||
|
@ -60,6 +60,11 @@
|
||||
"reason": "reasonValue",
|
||||
"nominatedNodeName": "nominatedNodeNameValue",
|
||||
"hostIP": "hostIPValue",
|
||||
"hostIPs": [
|
||||
{
|
||||
"ip": "ipValue"
|
||||
}
|
||||
],
|
||||
"podIP": "podIPValue",
|
||||
"podIPs": [
|
||||
{
|
||||
|
Binary file not shown.
@ -114,6 +114,8 @@ status:
|
||||
message: messageValue
|
||||
reason: reasonValue
|
||||
hostIP: hostIPValue
|
||||
hostIPs:
|
||||
- ip: ipValue
|
||||
initContainerStatuses:
|
||||
- containerID: containerIDValue
|
||||
image: imageValue
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
// HostIPApplyConfiguration represents an declarative configuration of the HostIP type for use
|
||||
// with apply.
|
||||
type HostIPApplyConfiguration struct {
|
||||
IP *string `json:"ip,omitempty"`
|
||||
}
|
||||
|
||||
// HostIPApplyConfiguration constructs an declarative configuration of the HostIP type for use with
|
||||
// apply.
|
||||
func HostIP() *HostIPApplyConfiguration {
|
||||
return &HostIPApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithIP sets the IP field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the IP field is set to the value of the last call.
|
||||
func (b *HostIPApplyConfiguration) WithIP(value string) *HostIPApplyConfiguration {
|
||||
b.IP = &value
|
||||
return b
|
||||
}
|
@ -32,6 +32,7 @@ type PodStatusApplyConfiguration struct {
|
||||
Reason *string `json:"reason,omitempty"`
|
||||
NominatedNodeName *string `json:"nominatedNodeName,omitempty"`
|
||||
HostIP *string `json:"hostIP,omitempty"`
|
||||
HostIPs []HostIPApplyConfiguration `json:"hostIPs,omitempty"`
|
||||
PodIP *string `json:"podIP,omitempty"`
|
||||
PodIPs []PodIPApplyConfiguration `json:"podIPs,omitempty"`
|
||||
StartTime *metav1.Time `json:"startTime,omitempty"`
|
||||
@ -100,6 +101,19 @@ func (b *PodStatusApplyConfiguration) WithHostIP(value string) *PodStatusApplyCo
|
||||
return b
|
||||
}
|
||||
|
||||
// WithHostIPs adds the given value to the HostIPs field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the HostIPs field.
|
||||
func (b *PodStatusApplyConfiguration) WithHostIPs(values ...*HostIPApplyConfiguration) *PodStatusApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithHostIPs")
|
||||
}
|
||||
b.HostIPs = append(b.HostIPs, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPodIP sets the PodIP field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the PodIP field is set to the value of the last call.
|
||||
|
@ -4623,6 +4623,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: ip
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.core.v1.HostIP
|
||||
map:
|
||||
fields:
|
||||
- name: ip
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.core.v1.HostPathVolumeSource
|
||||
map:
|
||||
fields:
|
||||
@ -5883,6 +5889,12 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: hostIP
|
||||
type:
|
||||
scalar: string
|
||||
- name: hostIPs
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.core.v1.HostIP
|
||||
elementRelationship: associative
|
||||
- name: initContainerStatuses
|
||||
type:
|
||||
list:
|
||||
|
@ -611,6 +611,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &applyconfigurationscorev1.GRPCActionApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("HostAlias"):
|
||||
return &applyconfigurationscorev1.HostAliasApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("HostIP"):
|
||||
return &applyconfigurationscorev1.HostIPApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("HostPathVolumeSource"):
|
||||
return &applyconfigurationscorev1.HostPathVolumeSourceApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("HTTPGetAction"):
|
||||
|
198
test/e2e_node/dual_stack_host_ips.go
Normal file
198
test/e2e_node/dual_stack_host_ips.go
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright 2022 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 e2enode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
kubefeatures "k8s.io/kubernetes/pkg/features"
|
||||
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
|
||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
||||
"k8s.io/kubernetes/test/e2e/network/common"
|
||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||
netutils "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
var _ = common.SIGDescribe("Dual Stack Host IP [Feature:PodHostIPs]", func() {
|
||||
f := framework.NewDefaultFramework("dualstack")
|
||||
|
||||
ginkgo.Context("when creating a Pod, it has PodHostIPs feature", func() {
|
||||
tempSetCurrentKubeletConfig(f, func(initialConfig *kubeletconfig.KubeletConfiguration) {
|
||||
initialConfig.FeatureGates = map[string]bool{
|
||||
string(features.PodHostIPs): false,
|
||||
}
|
||||
})
|
||||
ginkgo.It("should create pod, add host ips is empty", func() {
|
||||
e2eskipper.SkipIfFeatureGateEnabled(kubefeatures.PodHostIPs)
|
||||
|
||||
podName := "pod-dualstack-host-ips"
|
||||
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Labels: map[string]string{"test": "dualstack-host-ips"},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "dualstack-host-ips",
|
||||
Image: imageutils.GetE2EImage(imageutils.Agnhost),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ginkgo.By("submitting the pod to kubernetes")
|
||||
podClient := f.PodClient()
|
||||
p := podClient.CreateSync(pod)
|
||||
|
||||
gomega.Expect(p.Status.HostIP).ShouldNot(gomega.BeEquivalentTo(""))
|
||||
gomega.Expect(p.Status.HostIPs).Should(gomega.BeNil())
|
||||
|
||||
ginkgo.By("deleting the pod")
|
||||
err := podClient.Delete(context.TODO(), pod.Name, *metav1.NewDeleteOptions(30))
|
||||
framework.ExpectNoError(err, "failed to delete pod")
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Context("when creating a Pod, it has no PodHostIPs feature", func() {
|
||||
tempSetCurrentKubeletConfig(f, func(initialConfig *kubeletconfig.KubeletConfiguration) {
|
||||
initialConfig.FeatureGates = map[string]bool{
|
||||
string(features.PodHostIPs): true,
|
||||
}
|
||||
})
|
||||
ginkgo.It("should create pod, add ipv6 and ipv4 ip to host ips", func() {
|
||||
|
||||
podName := "pod-dualstack-host-ips"
|
||||
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Labels: map[string]string{"test": "dualstack-host-ips"},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "dualstack-host-ips",
|
||||
Image: imageutils.GetE2EImage(imageutils.Agnhost),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ginkgo.By("submitting the pod to kubernetes")
|
||||
podClient := f.PodClient()
|
||||
p := podClient.CreateSync(pod)
|
||||
|
||||
gomega.Expect(p.Status.HostIP).ShouldNot(gomega.BeEquivalentTo(""))
|
||||
gomega.Expect(p.Status.HostIPs).ShouldNot(gomega.BeNil())
|
||||
|
||||
// validate first ip in HostIPs is same as HostIP
|
||||
framework.ExpectEqual(p.Status.HostIP, p.Status.HostIPs[0].IP)
|
||||
if len(p.Status.HostIPs) > 1 {
|
||||
// assert 2 host ips belong to different families
|
||||
if netutils.IsIPv4String(p.Status.HostIPs[0].IP) == netutils.IsIPv4String(p.Status.HostIPs[1].IP) {
|
||||
framework.Failf("both internalIPs %s and %s belong to the same families", p.Status.HostIPs[0].IP, p.Status.HostIPs[1].IP)
|
||||
}
|
||||
}
|
||||
|
||||
nodeList, err := e2enode.GetReadySchedulableNodes(f.ClientSet)
|
||||
framework.ExpectNoError(err)
|
||||
for _, node := range nodeList.Items {
|
||||
if node.Name == p.Spec.NodeName {
|
||||
nodeIPs := []string{}
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == v1.NodeInternalIP {
|
||||
nodeIPs = append(nodeIPs, address.Address)
|
||||
}
|
||||
}
|
||||
gomega.Expect(p.Status.HostIPs).Should(gomega.Equal(nodeIPs))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ginkgo.By("deleting the pod")
|
||||
err = podClient.Delete(context.TODO(), pod.Name, *metav1.NewDeleteOptions(30))
|
||||
framework.ExpectNoError(err, "failed to delete pod")
|
||||
})
|
||||
|
||||
ginkgo.It("should provide hostIPs as an env var", func() {
|
||||
podName := "downward-api-" + string(uuid.NewUUID())
|
||||
env := []v1.EnvVar{
|
||||
{
|
||||
Name: "HOST_IPS",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
FieldRef: &v1.ObjectFieldSelector{
|
||||
APIVersion: "v1",
|
||||
FieldPath: "status.hostIPs",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectations := []string{
|
||||
fmt.Sprintf("HOST_IPS=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6),
|
||||
}
|
||||
|
||||
testDownwardAPI(f, podName, env, expectations)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func testDownwardAPI(f *framework.Framework, podName string, env []v1.EnvVar, expectations []string) {
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Labels: map[string]string{"name": podName},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "dapi-container",
|
||||
Image: imageutils.GetE2EImage(imageutils.BusyBox),
|
||||
Command: []string{"sh", "-c", "env"},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("250m"),
|
||||
v1.ResourceMemory: resource.MustParse("32Mi"),
|
||||
},
|
||||
Limits: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("1250m"),
|
||||
v1.ResourceMemory: resource.MustParse("64Mi"),
|
||||
},
|
||||
},
|
||||
Env: env,
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
},
|
||||
}
|
||||
|
||||
f.TestContainerOutputRegexp("downward api env vars", pod, 0, expectations)
|
||||
}
|
Loading…
Reference in New Issue
Block a user