rebase: update kubernetes and libraries to v1.22.0 version

Kubernetes v1.22 version has been released and this update
ceph csi dependencies to use the same version.

Signed-off-by: Humble Chirammal <hchiramm@redhat.com>
This commit is contained in:
Humble Chirammal
2021-08-09 12:49:24 +05:30
committed by mergify[bot]
parent e077c1fdf5
commit aa698bc3e1
759 changed files with 61864 additions and 6514 deletions

View File

@ -1,19 +1,8 @@
# See the OWNERS docs at https://go.k8s.io/owners
# approval on api packages bubbles to api-approvers
reviewers:
- thockin
- lavalamp
- smarterclayton
- deads2k
- caesarxuchao
- pmorie
- sttts
- saad-ali
- ncdc
- dims
- errordeveloper
- mml
- m1093782566
- kevin-wangzefeng
- sig-apps-api-reviewers
- sig-apps-api-approvers
labels:
- sig/apps

View File

@ -157,6 +157,13 @@ type StatefulSetSpec struct {
// consists of all revisions not represented by a currently applied
// StatefulSetSpec version. The default value is 10.
RevisionHistoryLimit *int32
// Minimum number of seconds for which a newly created pod should be ready
// without any of its container crashing for it to be considered available.
// Defaults to 0 (pod will be considered available as soon as it is ready)
// This is an alpha field and requires enabling StatefulSetMinReadySeconds feature gate.
// +optional
MinReadySeconds int32
}
// StatefulSetStatus represents the current state of a StatefulSet.
@ -196,6 +203,12 @@ type StatefulSetStatus struct {
// Represents the latest available observations of a statefulset's current state.
Conditions []StatefulSetCondition
// Total number of available pods (ready for at least minReadySeconds) targeted by this statefulset.
// This is an alpha field and requires enabling StatefulSetMinReadySeconds feature gate.
// Remove omitempty when graduating to beta
// +optional
AvailableReplicas int32
}
// StatefulSetConditionType describes the condition types of StatefulSets.
@ -532,7 +545,7 @@ type RollingUpdateDaemonSet struct {
// The maximum number of DaemonSet pods that can be unavailable during the
// update. Value can be an absolute number (ex: 5) or a percentage of total
// number of DaemonSet pods at the start of the update (ex: 10%). Absolute
// number is calculated from percentage by rounding down to a minimum of one.
// number is calculated from percentage by rounding up.
// This cannot be 0 if MaxSurge is 0
// Default value is 1.
// Example: when this is set to 30%, at most 30% of the total number of nodes
@ -564,7 +577,7 @@ type RollingUpdateDaemonSet struct {
// daemonset on any given node can double if the readiness check fails, and
// so resource intensive daemonsets should take into account that they may
// cause evictions during disruption.
// This is an alpha field and requires enabling DaemonSetUpdateSurge feature gate.
// This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.
// +optional
MaxSurge intstr.IntOrString
}

View File

@ -7,10 +7,8 @@ reviewers:
- wojtek-t
- deads2k
- caesarxuchao
- erictune
- sttts
- ncdc
- piosz
- dims
- errordeveloper
- mml

View File

@ -1,19 +1,8 @@
# See the OWNERS docs at https://go.k8s.io/owners
# approval on api packages bubbles to api-approvers
reviewers:
- thockin
- lavalamp
- smarterclayton
- wojtek-t
- deads2k
- caesarxuchao
- erictune
- sttts
- saad-ali
- ncdc
- soltysh
- dims
- errordeveloper
- mml
- sig-apps-api-reviewers
- sig-apps-api-approvers
labels:
- sig/apps

View File

@ -18,9 +18,18 @@ package batch
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
api "k8s.io/kubernetes/pkg/apis/core"
)
// JobTrackingFinalizer is a finalizer for Job's pods. It prevents them from
// being deleted before being accounted in the Job status.
// The apiserver and job controller use this string as a Job annotation, to
// mark Jobs that are being tracked using pod finalizers. Two releases after
// the JobTrackingWithFinalizers graduates to GA, JobTrackingFinalizer will
// no longer be used as a Job annotation.
const JobTrackingFinalizer = "batch.kubernetes.io/job-tracking"
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Job represents the configuration of a single job.
@ -183,9 +192,11 @@ type JobSpec struct {
// for each index.
// When value is `Indexed`, .spec.completions must be specified and
// `.spec.parallelism` must be less than or equal to 10^5.
// In addition, The Pod name takes the form
// `$(job-name)-$(index)-$(random-string)`,
// the Pod hostname takes the form `$(job-name)-$(index)`.
//
// This field is alpha-level and is only honored by servers that enable the
// IndexedJob feature gate. More completion modes can be added in the future.
// This field is beta-level. More completion modes can be added in the future.
// If the Job controller observes a mode that it doesn't recognize, the
// controller skips updates for the Job.
// +optional
@ -197,9 +208,11 @@ type JobSpec struct {
// false to true), the Job controller will delete all active Pods associated
// with this Job. Users must design their workload to gracefully handle this.
// Suspending a Job will reset the StartTime field of the Job, effectively
// resetting the ActiveDeadlineSeconds timer too. This is an alpha field and
// requires the SuspendJob feature gate to be enabled; otherwise this field
// may not be set to true. Defaults to false.
// resetting the ActiveDeadlineSeconds timer too. Defaults to false.
//
// This field is beta-level, gated by SuspendJob feature flag (enabled by
// default).
//
// +optional
Suspend *bool
}
@ -251,6 +264,38 @@ type JobStatus struct {
// represented as "1,3-5,7".
// +optional
CompletedIndexes string
// UncountedTerminatedPods holds the UIDs of Pods that have terminated but
// the job controller hasn't yet accounted for in the status counters.
//
// The job controller creates pods with a finalizer. When a pod terminates
// (succeeded or failed), the controller does three steps to account for it
// in the job status:
// (1) Add the pod UID to the corresponding array in this field.
// (2) Remove the pod finalizer.
// (3) Remove the pod UID from the array while increasing the corresponding
// counter.
//
// This field is alpha-level. The job controller only makes use of this field
// when the feature gate PodTrackingWithFinalizers is enabled.
// Old jobs might not be tracked using this field, in which case the field
// remains null.
// +optional
UncountedTerminatedPods *UncountedTerminatedPods
}
// UncountedTerminatedPods holds UIDs of Pods that have terminated but haven't
// been accounted in Job status counters.
type UncountedTerminatedPods struct {
// Succeeded holds UIDs of succeeded Pods.
// +listType=set
// +optional
Succeeded []types.UID
// Failed holds UIDs of failed Pods.
// +listType=set
// +optional
Failed []types.UID
}
// JobConditionType is a valid value for JobCondition.Type

View File

@ -23,6 +23,7 @@ package batch
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
types "k8s.io/apimachinery/pkg/types"
core "k8s.io/kubernetes/pkg/apis/core"
)
@ -312,6 +313,11 @@ func (in *JobStatus) DeepCopyInto(out *JobStatus) {
in, out := &in.CompletionTime, &out.CompletionTime
*out = (*in).DeepCopy()
}
if in.UncountedTerminatedPods != nil {
in, out := &in.UncountedTerminatedPods, &out.UncountedTerminatedPods
*out = new(UncountedTerminatedPods)
(*in).DeepCopyInto(*out)
}
return
}
@ -369,3 +375,29 @@ func (in *JobTemplateSpec) DeepCopy() *JobTemplateSpec {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UncountedTerminatedPods) DeepCopyInto(out *UncountedTerminatedPods) {
*out = *in
if in.Succeeded != nil {
in, out := &in.Succeeded, &out.Succeeded
*out = make([]types.UID, len(*in))
copy(*out, *in)
}
if in.Failed != nil {
in, out := &in.Failed, &out.Failed
*out = make([]types.UID, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UncountedTerminatedPods.
func (in *UncountedTerminatedPods) DeepCopy() *UncountedTerminatedPods {
if in == nil {
return nil
}
out := new(UncountedTerminatedPods)
in.DeepCopyInto(out)
return out
}

View File

@ -1,40 +1,4 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- erictune
- lavalamp
- smarterclayton
- thockin
- liggitt
reviewers:
- thockin
- lavalamp
- smarterclayton
- wojtek-t
- deads2k
- yujuhong
- brendandburns
- derekwaynecarr
- caesarxuchao
- vishh
- mikedanese
- liggitt
- erictune
- davidopp
- pmorie
- sttts
- dchen1107
- saad-ali
- luxas
- janetkuo
- justinsb
- pwittrock
- ncdc
- tallclair
- yifan-gu
- mwielgus
- soltysh
- piosz
- jsafrane
labels:
- sig/apps

View File

@ -23,9 +23,6 @@ const (
// webhook backend fails.
ImagePolicyFailedOpenKey string = "alpha.image-policy.k8s.io/failed-open"
// PodPresetOptOutAnnotationKey represents the annotation key for a pod to exempt itself from pod preset manipulation
PodPresetOptOutAnnotationKey string = "podpreset.admission.kubernetes.io/exclude"
// MirrorPodAnnotationKey represents the annotation key set by kubelets when creating mirror pods
MirrorPodAnnotationKey string = "kubernetes.io/config.mirror"
@ -122,7 +119,7 @@ const (
// pod deletion order.
// The implicit deletion cost for pods that don't set the annotation is 0, negative values are permitted.
//
// This annotation is alpha-level and is only honored when PodDeletionCost feature is enabled.
// This annotation is beta-level and is only honored when PodDeletionCost feature is enabled.
PodDeletionCost = "controller.kubernetes.io/pod-deletion-cost"
// AnnotationTopologyAwareHints can be used to enable or disable Topology

View File

@ -303,19 +303,22 @@ func IsStandardFinalizerName(str string) bool {
}
// GetAccessModesAsString returns a string representation of an array of access modes.
// modes, when present, are always in the same order: RWO,ROX,RWX.
// modes, when present, are always in the same order: RWO,ROX,RWX,RWOP.
func GetAccessModesAsString(modes []core.PersistentVolumeAccessMode) string {
modes = removeDuplicateAccessModes(modes)
modesStr := []string{}
if containsAccessMode(modes, core.ReadWriteOnce) {
if ContainsAccessMode(modes, core.ReadWriteOnce) {
modesStr = append(modesStr, "RWO")
}
if containsAccessMode(modes, core.ReadOnlyMany) {
if ContainsAccessMode(modes, core.ReadOnlyMany) {
modesStr = append(modesStr, "ROX")
}
if containsAccessMode(modes, core.ReadWriteMany) {
if ContainsAccessMode(modes, core.ReadWriteMany) {
modesStr = append(modesStr, "RWX")
}
if ContainsAccessMode(modes, core.ReadWriteOncePod) {
modesStr = append(modesStr, "RWOP")
}
return strings.Join(modesStr, ",")
}
@ -332,6 +335,8 @@ func GetAccessModesFromString(modes string) []core.PersistentVolumeAccessMode {
accessModes = append(accessModes, core.ReadOnlyMany)
case s == "RWX":
accessModes = append(accessModes, core.ReadWriteMany)
case s == "RWOP":
accessModes = append(accessModes, core.ReadWriteOncePod)
}
}
return accessModes
@ -341,14 +346,14 @@ func GetAccessModesFromString(modes string) []core.PersistentVolumeAccessMode {
func removeDuplicateAccessModes(modes []core.PersistentVolumeAccessMode) []core.PersistentVolumeAccessMode {
accessModes := []core.PersistentVolumeAccessMode{}
for _, m := range modes {
if !containsAccessMode(accessModes, m) {
if !ContainsAccessMode(accessModes, m) {
accessModes = append(accessModes, m)
}
}
return accessModes
}
func containsAccessMode(modes []core.PersistentVolumeAccessMode, mode core.PersistentVolumeAccessMode) bool {
func ContainsAccessMode(modes []core.PersistentVolumeAccessMode, mode core.PersistentVolumeAccessMode) bool {
for _, m := range modes {
if m == mode {
return true

View File

@ -96,7 +96,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&RangeAllocation{},
&ConfigMap{},
&ConfigMapList{},
&EphemeralContainers{},
)
return nil

View File

@ -450,13 +450,31 @@ type PersistentVolumeClaimSpec struct {
// This field can be used to specify either:
// * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)
// * An existing PVC (PersistentVolumeClaim)
// * An existing custom resource that implements data population (Alpha)
// In order to use custom resource types that implement data population,
// the AnyVolumeDataSource feature gate must be enabled.
// If the provisioner or an external controller can support the specified data source,
// it will create a new volume based on the contents of the specified data source.
// If the AnyVolumeDataSource feature gate is enabled, this field will always have
// the same contents as the DataSourceRef field.
// +optional
DataSource *TypedLocalObjectReference
// Specifies the object from which to populate the volume with data, if a non-empty
// volume is desired. This may be any local object from a non-empty API group (non
// core object) or a PersistentVolumeClaim object.
// When this field is specified, volume binding will only succeed if the type of
// the specified object matches some installed volume populator or dynamic
// provisioner.
// This field will replace the functionality of the DataSource field and as such
// if both fields are non-empty, they must have the same value. For backwards
// compatibility, both fields (DataSource and DataSourceRef) will be set to the same
// value automatically if one of them is empty and the other is non-empty.
// There are two important differences between DataSource and DataSourceRef:
// * While DataSource only allows two specific types of objects, DataSourceRef
// allows any non-core object, as well as PersistentVolumeClaim objects.
// * While DataSource ignores disallowed values (dropping them), DataSourceRef
// preserves all values, and generates an error if a disallowed value is
// specified.
// (Alpha) Using this field requires the AnyVolumeDataSource feature gate to be enabled.
// +optional
DataSourceRef *TypedLocalObjectReference
}
// PersistentVolumeClaimConditionType defines the condition of PV claim.
@ -511,6 +529,9 @@ const (
ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany"
// can be mounted in read/write mode to many hosts
ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany"
// can be mounted read/write mode to exactly 1 pod
// cannot be used in combination with other access modes
ReadWriteOncePod PersistentVolumeAccessMode = "ReadWriteOncePod"
)
// PersistentVolumePhase defines the phase in which a PV is
@ -1826,12 +1847,13 @@ type EnvVar struct {
Name string
// Optional: no more than one of the following may be specified.
// Optional: Defaults to ""; variable references $(VAR_NAME) are expanded
// using the previous defined environment variables in the container and
// using the previously defined environment variables in the container and
// any service environment variables. If a variable cannot be resolved,
// the reference in the input string will be unchanged. The $(VAR_NAME)
// syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped
// references will never be expanded, regardless of whether the variable
// exists or not.
// the reference in the input string will be unchanged. Double $$ are
// reduced to a single $, which allows for escaping the $(VAR_NAME)
// syntax: i.e. "$$(VAR_NAME)" will produce the string literal
// "$(VAR_NAME)". Escaped references will never be expanded,
// regardless of whether the variable exists or not.
// +optional
Value string
// Optional: Specifies a source the value of this var should come from.
@ -2028,7 +2050,7 @@ type Probe struct {
// value overrides the value provided by the pod spec.
// Value must be non-negative integer. The value zero indicates stop immediately via
// the kill signal (no opportunity to shut down).
// This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate.
// This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.
// +optional
TerminationGracePeriodSeconds *int64
}
@ -2102,16 +2124,18 @@ type Container struct {
Image string
// Optional: The docker image's entrypoint is used if this is not provided; cannot be updated.
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
// cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax
// can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
// regardless of whether the variable exists or not.
// cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
// to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
// produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
// of whether the variable exists or not.
// +optional
Command []string
// Optional: The docker image's cmd is used if this is not provided; cannot be updated.
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
// cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax
// can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
// regardless of whether the variable exists or not.
// cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
// to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
// produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
// of whether the variable exists or not.
// +optional
Args []string
// Optional: Defaults to Docker's default.
@ -2304,6 +2328,7 @@ const (
PodFailed PodPhase = "Failed"
// PodUnknown means that for some reason the state of the pod could not be obtained, typically due
// to an error in communicating with the host of the pod.
// Deprecated in v1.21: It isn't being set since 2015 (74da3b14b0c0f658b3bb8d2def5094686d0e9095)
PodUnknown PodPhase = "Unknown"
)
@ -2579,7 +2604,7 @@ type PodAffinityTerm struct {
// and the ones listed in the namespaces field.
// null selector and null or empty namespaces list means "this pod's namespace".
// An empty selector ({}) matches all namespaces.
// This field is alpha-level and is only honored when PodAffinityNamespaceSelector feature is enabled.
// This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled.
// +optional
NamespaceSelector *metav1.LabelSelector
}
@ -2832,14 +2857,14 @@ type PodSpec struct {
// If specified, all readiness gates will be evaluated for pod readiness.
// A pod is ready when all its containers are ready AND
// all conditions specified in the readiness gates have status equal to "True"
// More info: https://git.k8s.io/enhancements/keps/sig-network/0007-pod-ready%2B%2B.md
// More info: https://git.k8s.io/enhancements/keps/sig-network/580-pod-readiness-gates
// +optional
ReadinessGates []PodReadinessGate
// RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used
// to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run.
// If unset or empty, the "legacy" RuntimeClass will be used, which is an implicit class with an
// empty definition that uses the default runtime handler.
// More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class/README.md
// More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class
// +optional
RuntimeClassName *string
// Overhead represents the resource overhead associated with running a pod for a given RuntimeClass.
@ -2848,8 +2873,8 @@ type PodSpec struct {
// The RuntimeClass admission controller will reject Pod create requests which have the overhead already
// set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value
// defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero.
// More info: https://git.k8s.io/enhancements/keps/sig-node/20190226-pod-overhead.md
// This field is alpha-level as of Kubernetes v1.16, and is only honored by servers that enable the PodOverhead feature.
// More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead
// This field is beta-level as of Kubernetes v1.18, and is only honored by servers that enable the PodOverhead feature.
// +optional
Overhead ResourceList
// EnableServiceLinks indicates whether information about services should be injected into pod's
@ -3078,16 +3103,18 @@ type EphemeralContainerCommon struct {
Image string
// Optional: The docker image's entrypoint is used if this is not provided; cannot be updated.
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
// cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax
// can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
// regardless of whether the variable exists or not.
// cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
// to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
// produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
// of whether the variable exists or not.
// +optional
Command []string
// Optional: The docker image's cmd is used if this is not provided; cannot be updated.
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
// cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax
// can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
// regardless of whether the variable exists or not.
// cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
// to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
// produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
// of whether the variable exists or not.
// +optional
Args []string
// Optional: Defaults to Docker's default.
@ -3134,7 +3161,8 @@ type EphemeralContainerCommon struct {
TerminationMessagePolicy TerminationMessagePolicy
// Required: Policy for pulling images for this container
ImagePullPolicy PullPolicy
// SecurityContext is not allowed for ephemeral containers.
// Optional: SecurityContext defines the security options the ephemeral container should be run with.
// If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.
// +optional
SecurityContext *SecurityContext
@ -3554,11 +3582,6 @@ type LoadBalancerIngress struct {
Ports []PortStatus
}
const (
// MaxServiceTopologyKeys is the largest number of topology keys allowed on a service
MaxServiceTopologyKeys = 16
)
// IPFamily represents the IP Family (IPv4 or IPv6). This type is used
// to express the family of an IP expressed by a type (e.g. service.spec.ipFamilies).
type IPFamily string
@ -3726,29 +3749,14 @@ type ServiceSpec struct {
// +optional
PublishNotReadyAddresses bool
// topologyKeys is a preference-order list of topology keys which
// implementations of services should use to preferentially sort endpoints
// when accessing this Service, it can not be used at the same time as
// externalTrafficPolicy=Local.
// Topology keys must be valid label keys and at most 16 keys may be specified.
// Endpoints are chosen based on the first topology key with available backends.
// If this field is specified and all entries have no backends that match
// the topology of the client, the service has no backends for that client
// and connections should fail.
// The special value "*" may be used to mean "any topology". This catch-all
// value, if used, only makes sense as the last value in the list.
// If this is not specified or empty, no topology constraints will be applied.
// This field is alpha-level and is only honored by servers that enable the ServiceTopology feature.
// This field is deprecated and will be removed in a future version.
// +optional
TopologyKeys []string
// allocateLoadBalancerNodePorts defines if NodePorts will be automatically
// allocated for services with type LoadBalancer. Default is "true". It may be
// set to "false" if the cluster load-balancer does not rely on NodePorts.
// allocateLoadBalancerNodePorts may only be set for services with type LoadBalancer
// and will be cleared if the type is changed to any other type.
// This field is alpha-level and is only honored by servers that enable the ServiceLBNodePortControl feature.
// allocated for services with type LoadBalancer. Default is "true". It
// may be set to "false" if the cluster load-balancer does not rely on
// NodePorts. If the caller requests specific NodePorts (by specifying a
// value), those requests will be respected, regardless of this field.
// This field may only be set for services with type LoadBalancer and will
// be cleared if the type is changed to any other type.
// This field is beta-level and is only honored by servers that enable the ServiceLBNodePortControl feature.
// +optional
AllocateLoadBalancerNodePorts *bool
@ -4200,6 +4208,7 @@ type PodSignature struct {
// ContainerImage describe a container image
type ContainerImage struct {
// Names by which this image is known.
// +optional
Names []string
// The size of the image in bytes.
// +optional
@ -4255,11 +4264,44 @@ type NodeAddressType string
// These are valid values of node address type
const (
NodeHostName NodeAddressType = "Hostname"
NodeExternalIP NodeAddressType = "ExternalIP"
NodeInternalIP NodeAddressType = "InternalIP"
NodeExternalDNS NodeAddressType = "ExternalDNS"
// NodeHostName identifies a name of the node. Although every node can be assumed
// to have a NodeAddress of this type, its exact syntax and semantics are not
// defined, and are not consistent between different clusters.
NodeHostName NodeAddressType = "Hostname"
// NodeInternalIP identifies an IP address which is assigned to one of the node's
// network interfaces. Every node should have at least one address of this type.
//
// An internal IP is normally expected to be reachable from every other node, but
// may not be visible to hosts outside the cluster. By default it is assumed that
// kube-apiserver can reach node internal IPs, though it is possible to configure
// clusters where this is not the case.
//
// NodeInternalIP is the default type of node IP, and does not necessarily imply
// that the IP is ONLY reachable internally. If a node has multiple internal IPs,
// no specific semantics are assigned to the additional IPs.
NodeInternalIP NodeAddressType = "InternalIP"
// NodeExternalIP identifies an IP address which is, in some way, intended to be
// more usable from outside the cluster then an internal IP, though no specific
// semantics are defined. It may be a globally routable IP, though it is not
// required to be.
//
// External IPs may be assigned directly to an interface on the node, like a
// NodeInternalIP, or alternatively, packets sent to the external IP may be NAT'ed
// to an internal node IP rather than being delivered directly (making the IP less
// efficient for node-to-node traffic than a NodeInternalIP).
NodeExternalIP NodeAddressType = "ExternalIP"
// NodeInternalDNS identifies a DNS name which resolves to an IP address which has
// the characteristics of a NodeInternalIP. The IP it resolves to may or may not
// be a listed NodeInternalIP address.
NodeInternalDNS NodeAddressType = "InternalDNS"
// NodeExternalDNS identifies a DNS name which resolves to an IP address which has
// the characteristics of a NodeExternalIP. The IP it resolves to may or may not
// be a listed NodeExternalIP address.
NodeExternalDNS NodeAddressType = "ExternalDNS"
)
// NodeAddress represents node's address
@ -4439,20 +4481,6 @@ type Binding struct {
Target ObjectReference
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// EphemeralContainers is a list of ephemeral containers used with the Pod ephemeralcontainers subresource.
type EphemeralContainers struct {
metav1.TypeMeta
// +optional
metav1.ObjectMeta
// A list of ephemeral containers associated with this pod. New ephemeral containers
// may be appended to this list, but existing ephemeral containers may not be removed
// or modified.
EphemeralContainers []EphemeralContainer
}
// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
type Preconditions struct {
// Specifies the target UID.
@ -4900,7 +4928,7 @@ const (
// Match all pod objects that have priority class mentioned
ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass"
// Match all pod objects that have cross-namespace pod (anti)affinity mentioned
// This is an alpha feature enabled by the PodAffinityNamespaceSelector feature flag.
// This is a beta feature enabled by the PodAffinityNamespaceSelector feature flag.
ResourceQuotaScopeCrossNamespacePodAffinity ResourceQuotaScope = "CrossNamespacePodAffinity"
)
@ -5364,6 +5392,16 @@ type WindowsSecurityContextOptions struct {
// PodSecurityContext, the value specified in SecurityContext takes precedence.
// +optional
RunAsUserName *string
// HostProcess determines if a container should be run as a 'Host Process' container.
// This field is alpha-level and will only be honored by components that enable the
// WindowsHostProcessContainers feature flag. Setting this field without the feature
// flag will result in errors when validating the Pod. All of a Pod's containers must
// have the same effective HostProcess value (it is not allowed to have a mix of HostProcess
// containers and non-HostProcess containers). In addition, if HostProcess is true
// then HostNetwork must also be set to true.
// +optional
HostProcess *bool
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -13,7 +13,6 @@ reviewers:
- vishh
- mikedanese
- liggitt
- erictune
- davidopp
- pmorie
- sttts

View File

@ -170,19 +170,22 @@ func ingressEqual(lhs, rhs *v1.LoadBalancerIngress) bool {
}
// GetAccessModesAsString returns a string representation of an array of access modes.
// modes, when present, are always in the same order: RWO,ROX,RWX.
// modes, when present, are always in the same order: RWO,ROX,RWX,RWOP.
func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string {
modes = removeDuplicateAccessModes(modes)
modesStr := []string{}
if containsAccessMode(modes, v1.ReadWriteOnce) {
if ContainsAccessMode(modes, v1.ReadWriteOnce) {
modesStr = append(modesStr, "RWO")
}
if containsAccessMode(modes, v1.ReadOnlyMany) {
if ContainsAccessMode(modes, v1.ReadOnlyMany) {
modesStr = append(modesStr, "ROX")
}
if containsAccessMode(modes, v1.ReadWriteMany) {
if ContainsAccessMode(modes, v1.ReadWriteMany) {
modesStr = append(modesStr, "RWX")
}
if ContainsAccessMode(modes, v1.ReadWriteOncePod) {
modesStr = append(modesStr, "RWOP")
}
return strings.Join(modesStr, ",")
}
@ -199,6 +202,8 @@ func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode {
accessModes = append(accessModes, v1.ReadOnlyMany)
case s == "RWX":
accessModes = append(accessModes, v1.ReadWriteMany)
case s == "RWOP":
accessModes = append(accessModes, v1.ReadWriteOncePod)
}
}
return accessModes
@ -208,14 +213,14 @@ func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode {
func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
accessModes := []v1.PersistentVolumeAccessMode{}
for _, m := range modes {
if !containsAccessMode(accessModes, m) {
if !ContainsAccessMode(accessModes, m) {
accessModes = append(accessModes, m)
}
}
return accessModes
}
func containsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
for _, m := range modes {
if m == mode {
return true

View File

@ -531,16 +531,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.EphemeralContainers)(nil), (*core.EphemeralContainers)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_EphemeralContainers_To_core_EphemeralContainers(a.(*v1.EphemeralContainers), b.(*core.EphemeralContainers), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*core.EphemeralContainers)(nil), (*v1.EphemeralContainers)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_core_EphemeralContainers_To_v1_EphemeralContainers(a.(*core.EphemeralContainers), b.(*v1.EphemeralContainers), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.EphemeralVolumeSource)(nil), (*core.EphemeralVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_EphemeralVolumeSource_To_core_EphemeralVolumeSource(a.(*v1.EphemeralVolumeSource), b.(*core.EphemeralVolumeSource), scope)
}); err != nil {
@ -3522,28 +3512,6 @@ func Convert_core_EphemeralContainerCommon_To_v1_EphemeralContainerCommon(in *co
return autoConvert_core_EphemeralContainerCommon_To_v1_EphemeralContainerCommon(in, out, s)
}
func autoConvert_v1_EphemeralContainers_To_core_EphemeralContainers(in *v1.EphemeralContainers, out *core.EphemeralContainers, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
out.EphemeralContainers = *(*[]core.EphemeralContainer)(unsafe.Pointer(&in.EphemeralContainers))
return nil
}
// Convert_v1_EphemeralContainers_To_core_EphemeralContainers is an autogenerated conversion function.
func Convert_v1_EphemeralContainers_To_core_EphemeralContainers(in *v1.EphemeralContainers, out *core.EphemeralContainers, s conversion.Scope) error {
return autoConvert_v1_EphemeralContainers_To_core_EphemeralContainers(in, out, s)
}
func autoConvert_core_EphemeralContainers_To_v1_EphemeralContainers(in *core.EphemeralContainers, out *v1.EphemeralContainers, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
out.EphemeralContainers = *(*[]v1.EphemeralContainer)(unsafe.Pointer(&in.EphemeralContainers))
return nil
}
// Convert_core_EphemeralContainers_To_v1_EphemeralContainers is an autogenerated conversion function.
func Convert_core_EphemeralContainers_To_v1_EphemeralContainers(in *core.EphemeralContainers, out *v1.EphemeralContainers, s conversion.Scope) error {
return autoConvert_core_EphemeralContainers_To_v1_EphemeralContainers(in, out, s)
}
func autoConvert_v1_EphemeralVolumeSource_To_core_EphemeralVolumeSource(in *v1.EphemeralVolumeSource, out *core.EphemeralVolumeSource, s conversion.Scope) error {
out.VolumeClaimTemplate = (*core.PersistentVolumeClaimTemplate)(unsafe.Pointer(in.VolumeClaimTemplate))
return nil
@ -5149,6 +5117,7 @@ func autoConvert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec(
out.StorageClassName = (*string)(unsafe.Pointer(in.StorageClassName))
out.VolumeMode = (*core.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
out.DataSource = (*core.TypedLocalObjectReference)(unsafe.Pointer(in.DataSource))
out.DataSourceRef = (*core.TypedLocalObjectReference)(unsafe.Pointer(in.DataSourceRef))
return nil
}
@ -5167,6 +5136,7 @@ func autoConvert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(
out.StorageClassName = (*string)(unsafe.Pointer(in.StorageClassName))
out.VolumeMode = (*v1.PersistentVolumeMode)(unsafe.Pointer(in.VolumeMode))
out.DataSource = (*v1.TypedLocalObjectReference)(unsafe.Pointer(in.DataSource))
out.DataSourceRef = (*v1.TypedLocalObjectReference)(unsafe.Pointer(in.DataSourceRef))
return nil
}
@ -7631,7 +7601,6 @@ func autoConvert_v1_ServiceSpec_To_core_ServiceSpec(in *v1.ServiceSpec, out *cor
out.HealthCheckNodePort = in.HealthCheckNodePort
out.PublishNotReadyAddresses = in.PublishNotReadyAddresses
out.SessionAffinityConfig = (*core.SessionAffinityConfig)(unsafe.Pointer(in.SessionAffinityConfig))
out.TopologyKeys = *(*[]string)(unsafe.Pointer(&in.TopologyKeys))
out.IPFamilies = *(*[]core.IPFamily)(unsafe.Pointer(&in.IPFamilies))
out.IPFamilyPolicy = (*core.IPFamilyPolicyType)(unsafe.Pointer(in.IPFamilyPolicy))
out.AllocateLoadBalancerNodePorts = (*bool)(unsafe.Pointer(in.AllocateLoadBalancerNodePorts))
@ -7662,7 +7631,6 @@ func autoConvert_core_ServiceSpec_To_v1_ServiceSpec(in *core.ServiceSpec, out *v
out.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy)
out.HealthCheckNodePort = in.HealthCheckNodePort
out.PublishNotReadyAddresses = in.PublishNotReadyAddresses
out.TopologyKeys = *(*[]string)(unsafe.Pointer(&in.TopologyKeys))
out.AllocateLoadBalancerNodePorts = (*bool)(unsafe.Pointer(in.AllocateLoadBalancerNodePorts))
out.LoadBalancerClass = (*string)(unsafe.Pointer(in.LoadBalancerClass))
out.InternalTrafficPolicy = (*v1.ServiceInternalTrafficPolicyType)(unsafe.Pointer(in.InternalTrafficPolicy))
@ -8244,6 +8212,7 @@ func autoConvert_v1_WindowsSecurityContextOptions_To_core_WindowsSecurityContext
out.GMSACredentialSpecName = (*string)(unsafe.Pointer(in.GMSACredentialSpecName))
out.GMSACredentialSpec = (*string)(unsafe.Pointer(in.GMSACredentialSpec))
out.RunAsUserName = (*string)(unsafe.Pointer(in.RunAsUserName))
out.HostProcess = (*bool)(unsafe.Pointer(in.HostProcess))
return nil
}
@ -8256,6 +8225,7 @@ func autoConvert_core_WindowsSecurityContextOptions_To_v1_WindowsSecurityContext
out.GMSACredentialSpecName = (*string)(unsafe.Pointer(in.GMSACredentialSpecName))
out.GMSACredentialSpec = (*string)(unsafe.Pointer(in.GMSACredentialSpec))
out.RunAsUserName = (*string)(unsafe.Pointer(in.RunAsUserName))
out.HostProcess = (*bool)(unsafe.Pointer(in.HostProcess))
return nil
}

View File

@ -33,7 +33,6 @@ func RegisterDefaults(scheme *runtime.Scheme) error {
scheme.AddTypeDefaultingFunc(&v1.ConfigMapList{}, func(obj interface{}) { SetObjectDefaults_ConfigMapList(obj.(*v1.ConfigMapList)) })
scheme.AddTypeDefaultingFunc(&v1.Endpoints{}, func(obj interface{}) { SetObjectDefaults_Endpoints(obj.(*v1.Endpoints)) })
scheme.AddTypeDefaultingFunc(&v1.EndpointsList{}, func(obj interface{}) { SetObjectDefaults_EndpointsList(obj.(*v1.EndpointsList)) })
scheme.AddTypeDefaultingFunc(&v1.EphemeralContainers{}, func(obj interface{}) { SetObjectDefaults_EphemeralContainers(obj.(*v1.EphemeralContainers)) })
scheme.AddTypeDefaultingFunc(&v1.LimitRange{}, func(obj interface{}) { SetObjectDefaults_LimitRange(obj.(*v1.LimitRange)) })
scheme.AddTypeDefaultingFunc(&v1.LimitRangeList{}, func(obj interface{}) { SetObjectDefaults_LimitRangeList(obj.(*v1.LimitRangeList)) })
scheme.AddTypeDefaultingFunc(&v1.Namespace{}, func(obj interface{}) { SetObjectDefaults_Namespace(obj.(*v1.Namespace)) })
@ -85,59 +84,6 @@ func SetObjectDefaults_EndpointsList(in *v1.EndpointsList) {
}
}
func SetObjectDefaults_EphemeralContainers(in *v1.EphemeralContainers) {
for i := range in.EphemeralContainers {
a := &in.EphemeralContainers[i]
SetDefaults_EphemeralContainer(a)
for j := range a.EphemeralContainerCommon.Ports {
b := &a.EphemeralContainerCommon.Ports[j]
if b.Protocol == "" {
b.Protocol = "TCP"
}
}
for j := range a.EphemeralContainerCommon.Env {
b := &a.EphemeralContainerCommon.Env[j]
if b.ValueFrom != nil {
if b.ValueFrom.FieldRef != nil {
SetDefaults_ObjectFieldSelector(b.ValueFrom.FieldRef)
}
}
}
SetDefaults_ResourceList(&a.EphemeralContainerCommon.Resources.Limits)
SetDefaults_ResourceList(&a.EphemeralContainerCommon.Resources.Requests)
if a.EphemeralContainerCommon.LivenessProbe != nil {
SetDefaults_Probe(a.EphemeralContainerCommon.LivenessProbe)
if a.EphemeralContainerCommon.LivenessProbe.Handler.HTTPGet != nil {
SetDefaults_HTTPGetAction(a.EphemeralContainerCommon.LivenessProbe.Handler.HTTPGet)
}
}
if a.EphemeralContainerCommon.ReadinessProbe != nil {
SetDefaults_Probe(a.EphemeralContainerCommon.ReadinessProbe)
if a.EphemeralContainerCommon.ReadinessProbe.Handler.HTTPGet != nil {
SetDefaults_HTTPGetAction(a.EphemeralContainerCommon.ReadinessProbe.Handler.HTTPGet)
}
}
if a.EphemeralContainerCommon.StartupProbe != nil {
SetDefaults_Probe(a.EphemeralContainerCommon.StartupProbe)
if a.EphemeralContainerCommon.StartupProbe.Handler.HTTPGet != nil {
SetDefaults_HTTPGetAction(a.EphemeralContainerCommon.StartupProbe.Handler.HTTPGet)
}
}
if a.EphemeralContainerCommon.Lifecycle != nil {
if a.EphemeralContainerCommon.Lifecycle.PostStart != nil {
if a.EphemeralContainerCommon.Lifecycle.PostStart.HTTPGet != nil {
SetDefaults_HTTPGetAction(a.EphemeralContainerCommon.Lifecycle.PostStart.HTTPGet)
}
}
if a.EphemeralContainerCommon.Lifecycle.PreStop != nil {
if a.EphemeralContainerCommon.Lifecycle.PreStop.HTTPGet != nil {
SetDefaults_HTTPGetAction(a.EphemeralContainerCommon.Lifecycle.PreStop.HTTPGet)
}
}
}
}
}
func SetObjectDefaults_LimitRange(in *v1.LimitRange) {
for i := range in.Spec.Limits {
a := &in.Spec.Limits[i]

View File

@ -13,7 +13,6 @@ reviewers:
- vishh
- mikedanese
- liggitt
- erictune
- davidopp
- pmorie
- sttts

View File

@ -109,7 +109,7 @@ func validateV1EventSeries(event *core.Event) field.ErrorList {
zeroTime := time.Time{}
if event.Series != nil {
if event.Series.Count < 2 {
allErrs = append(allErrs, field.Invalid(field.NewPath("series.count"), "", fmt.Sprintf("should be at least 2")))
allErrs = append(allErrs, field.Invalid(field.NewPath("series.count"), "", "should be at least 2"))
}
if event.Series.LastObservedTime.Time == zeroTime {
allErrs = append(allErrs, field.Required(field.NewPath("series.lastObservedTime"), ""))

View File

@ -35,7 +35,6 @@ import (
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr"
@ -49,7 +48,6 @@ import (
"k8s.io/kubernetes/pkg/apis/core/helper"
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/cluster/ports"
"k8s.io/kubernetes/pkg/features"
@ -86,6 +84,7 @@ var allowedEphemeralContainerFields = map[string]bool{
"TerminationMessagePath": true,
"TerminationMessagePolicy": true,
"ImagePullPolicy": true,
"SecurityContext": true,
"Stdin": true,
"StdinOnce": true,
"TTY": true,
@ -283,7 +282,7 @@ var ValidateClusterName = apimachineryvalidation.ValidateClusterName
// (where it should be) and this file.
var ValidateClassName = apimachineryvalidation.NameIsDNSSubdomain
// ValidatePiorityClassName can be used to check whether the given priority
// ValidatePriorityClassName can be used to check whether the given priority
// class name is valid.
var ValidatePriorityClassName = apimachineryvalidation.NameIsDNSSubdomain
@ -1217,8 +1216,8 @@ func validateMountPropagation(mountPropagation *core.MountPropagationMode, conta
}
if container == nil {
// The container is not available yet, e.g. during validation of
// PodPreset. Stop validation now, Pod validation will refuse final
// The container is not available yet.
// Stop validation now, Pod validation will refuse final
// Pods with Bidirectional propagation in non-privileged containers.
return allErrs
}
@ -1609,22 +1608,23 @@ func validateEphemeralVolumeSource(ephemeral *core.EphemeralVolumeSource, fldPat
if ephemeral.VolumeClaimTemplate == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("volumeClaimTemplate"), ""))
} else {
allErrs = append(allErrs, ValidatePersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, fldPath.Child("volumeClaimTemplate"))...)
opts := ValidationOptionsForPersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, nil)
allErrs = append(allErrs, ValidatePersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, fldPath.Child("volumeClaimTemplate"), opts)...)
}
return allErrs
}
// ValidatePersistentVolumeClaimTemplate verifies that the embedded object meta and spec are valid.
// Checking of the object data is very minimal because only labels and annotations are used.
func ValidatePersistentVolumeClaimTemplate(claimTemplate *core.PersistentVolumeClaimTemplate, fldPath *field.Path) field.ErrorList {
func ValidatePersistentVolumeClaimTemplate(claimTemplate *core.PersistentVolumeClaimTemplate, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
allErrs := validatePersistentVolumeClaimTemplateObjectMeta(&claimTemplate.ObjectMeta, fldPath.Child("metadata"))
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&claimTemplate.Spec, fldPath.Child("spec"))...)
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&claimTemplate.Spec, fldPath.Child("spec"), opts)...)
return allErrs
}
func validatePersistentVolumeClaimTemplateObjectMeta(objMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
allErrs := apimachineryvalidation.ValidateAnnotations(objMeta.Annotations, fldPath.Child("annotations"))
allErrs = append(allErrs, v1validation.ValidateLabels(objMeta.Labels, fldPath.Child("labels"))...)
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(objMeta.Labels, fldPath.Child("labels"))...)
// All other fields are not supported and thus must not be set
// to avoid confusion. We could reject individual fields,
// but then adding a new one to ObjectMeta wouldn't be checked
@ -1639,6 +1639,12 @@ var allowedPVCTemplateObjectMetaFields = map[string]bool{
"Labels": true,
}
// PersistentVolumeSpecValidationOptions contains the different settings for PeristentVolume validation
type PersistentVolumeSpecValidationOptions struct {
// Allow spec to contain the "ReadWiteOncePod" access mode
AllowReadWriteOncePod bool
}
// ValidatePersistentVolumeName checks that a name is appropriate for a
// PersistentVolumeName object.
var ValidatePersistentVolumeName = apimachineryvalidation.NameIsDNSSubdomain
@ -1649,7 +1655,22 @@ var supportedReclaimPolicy = sets.NewString(string(core.PersistentVolumeReclaimD
var supportedVolumeModes = sets.NewString(string(core.PersistentVolumeBlock), string(core.PersistentVolumeFilesystem))
func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName string, validateInlinePersistentVolumeSpec bool, fldPath *field.Path) field.ErrorList {
func ValidationOptionsForPersistentVolume(pv, oldPv *core.PersistentVolume) PersistentVolumeSpecValidationOptions {
opts := PersistentVolumeSpecValidationOptions{
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
}
if oldPv == nil {
// If there's no old PV, use the options based solely on feature enablement
return opts
}
if helper.ContainsAccessMode(oldPv.Spec.AccessModes, core.ReadWriteOncePod) {
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
opts.AllowReadWriteOncePod = true
}
return opts
}
func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName string, validateInlinePersistentVolumeSpec bool, fldPath *field.Path, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
if validateInlinePersistentVolumeSpec {
@ -1667,10 +1688,26 @@ func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName stri
if len(pvSpec.AccessModes) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), ""))
}
expandedSupportedAccessModes := sets.StringKeySet(supportedAccessModes)
if opts.AllowReadWriteOncePod {
expandedSupportedAccessModes.Insert(string(core.ReadWriteOncePod))
}
foundReadWriteOncePod, foundNonReadWriteOncePod := false, false
for _, mode := range pvSpec.AccessModes {
if !supportedAccessModes.Has(string(mode)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, supportedAccessModes.List()))
if !expandedSupportedAccessModes.Has(string(mode)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, expandedSupportedAccessModes.List()))
}
if mode == core.ReadWriteOncePod {
foundReadWriteOncePod = true
} else if supportedAccessModes.Has(string(mode)) {
foundNonReadWriteOncePod = true
}
}
if foundReadWriteOncePod && foundNonReadWriteOncePod {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("accessModes"), "may not use ReadWriteOncePod with other access modes"))
}
if !validateInlinePersistentVolumeSpec {
@ -1923,17 +1960,17 @@ func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName stri
return allErrs
}
func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
func ValidatePersistentVolume(pv *core.PersistentVolume, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
metaPath := field.NewPath("metadata")
allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath)
allErrs = append(allErrs, ValidatePersistentVolumeSpec(&pv.Spec, pv.ObjectMeta.Name, false, field.NewPath("spec"))...)
allErrs = append(allErrs, ValidatePersistentVolumeSpec(&pv.Spec, pv.ObjectMeta.Name, false, field.NewPath("spec"), opts)...)
return allErrs
}
// ValidatePersistentVolumeUpdate tests to see if the update is legal for an end user to make.
// newPv is updated with fields that cannot be changed.
func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.ErrorList {
allErrs := ValidatePersistentVolume(newPv)
func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
allErrs := ValidatePersistentVolume(newPv, opts)
// if oldPV does not have ControllerExpandSecretRef then allow it to be set
if (oldPv.Spec.CSI != nil && oldPv.Spec.CSI.ControllerExpandSecretRef == nil) &&
@ -1966,15 +2003,72 @@ func ValidatePersistentVolumeStatusUpdate(newPv, oldPv *core.PersistentVolume) f
return allErrs
}
// PersistentVolumeClaimSpecValidationOptions contains the different settings for PersistentVolumeClaim validation
type PersistentVolumeClaimSpecValidationOptions struct {
// Allow spec to contain the "ReadWiteOncePod" access mode
AllowReadWriteOncePod bool
}
func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions {
opts := PersistentVolumeClaimSpecValidationOptions{
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
}
if oldPvc == nil {
// If there's no old PVC, use the options based solely on feature enablement
return opts
}
if helper.ContainsAccessMode(oldPvc.Spec.AccessModes, core.ReadWriteOncePod) {
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
opts.AllowReadWriteOncePod = true
}
return opts
}
func ValidationOptionsForPersistentVolumeClaimTemplate(claimTemplate, oldClaimTemplate *core.PersistentVolumeClaimTemplate) PersistentVolumeClaimSpecValidationOptions {
opts := PersistentVolumeClaimSpecValidationOptions{
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
}
if oldClaimTemplate == nil {
// If there's no old PVC template, use the options based solely on feature enablement
return opts
}
if helper.ContainsAccessMode(oldClaimTemplate.Spec.AccessModes, core.ReadWriteOncePod) {
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
opts.AllowReadWriteOncePod = true
}
return opts
}
// ValidatePersistentVolumeClaim validates a PersistentVolumeClaim
func ValidatePersistentVolumeClaim(pvc *core.PersistentVolumeClaim) field.ErrorList {
func ValidatePersistentVolumeClaim(pvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
allErrs := ValidateObjectMeta(&pvc.ObjectMeta, true, ValidatePersistentVolumeName, field.NewPath("metadata"))
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&pvc.Spec, field.NewPath("spec"))...)
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&pvc.Spec, field.NewPath("spec"), opts)...)
return allErrs
}
// validateDataSource validates a DataSource/DataSourceRef in a PersistentVolumeClaimSpec
func validateDataSource(dataSource *core.TypedLocalObjectReference, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(dataSource.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
}
if len(dataSource.Kind) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
}
apiGroup := ""
if dataSource.APIGroup != nil {
apiGroup = *dataSource.APIGroup
}
if len(apiGroup) == 0 && dataSource.Kind != "PersistentVolumeClaim" {
allErrs = append(allErrs, field.Invalid(fldPath, dataSource.Kind, ""))
}
return allErrs
}
// ValidatePersistentVolumeClaimSpec validates a PersistentVolumeClaimSpec
func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fldPath *field.Path) field.ErrorList {
func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
if len(spec.AccessModes) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required"))
@ -1982,11 +2076,28 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
if spec.Selector != nil {
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
}
expandedSupportedAccessModes := sets.StringKeySet(supportedAccessModes)
if opts.AllowReadWriteOncePod {
expandedSupportedAccessModes.Insert(string(core.ReadWriteOncePod))
}
foundReadWriteOncePod, foundNonReadWriteOncePod := false, false
for _, mode := range spec.AccessModes {
if mode != core.ReadWriteOnce && mode != core.ReadOnlyMany && mode != core.ReadWriteMany {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, supportedAccessModes.List()))
if !expandedSupportedAccessModes.Has(string(mode)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, expandedSupportedAccessModes.List()))
}
if mode == core.ReadWriteOncePod {
foundReadWriteOncePod = true
} else if supportedAccessModes.Has(string(mode)) {
foundNonReadWriteOncePod = true
}
}
if foundReadWriteOncePod && foundNonReadWriteOncePod {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("accessModes"), "may not use ReadWriteOncePod with other access modes"))
}
storageValue, ok := spec.Resources.Requests[core.ResourceStorage]
if !ok {
allErrs = append(allErrs, field.Required(fldPath.Child("resources").Key(string(core.ResourceStorage)), ""))
@ -2006,18 +2117,15 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
}
if spec.DataSource != nil {
if len(spec.DataSource.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("dataSource", "name"), ""))
}
if len(spec.DataSource.Kind) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("dataSource", "kind"), ""))
}
apiGroup := ""
if spec.DataSource.APIGroup != nil {
apiGroup = *spec.DataSource.APIGroup
}
if len(apiGroup) == 0 && spec.DataSource.Kind != "PersistentVolumeClaim" {
allErrs = append(allErrs, field.Invalid(fldPath.Child("dataSource"), spec.DataSource.Kind, ""))
allErrs = append(allErrs, validateDataSource(spec.DataSource, fldPath.Child("dataSource"))...)
}
if spec.DataSourceRef != nil {
allErrs = append(allErrs, validateDataSource(spec.DataSourceRef, fldPath.Child("dataSourceRef"))...)
}
if spec.DataSource != nil && spec.DataSourceRef != nil {
if !apiequality.Semantic.DeepEqual(spec.DataSource, spec.DataSourceRef) {
allErrs = append(allErrs, field.Invalid(fldPath, fldPath.Child("dataSource"),
"must match dataSourceRef"))
}
}
@ -2025,9 +2133,9 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
}
// ValidatePersistentVolumeClaimUpdate validates an update to a PersistentVolumeClaim
func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeClaim) field.ErrorList {
func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidatePersistentVolumeClaim(newPvc)...)
allErrs = append(allErrs, ValidatePersistentVolumeClaim(newPvc, opts)...)
newPvcClone := newPvc.DeepCopy()
oldPvcClone := oldPvc.DeepCopy()
@ -2547,6 +2655,9 @@ func validateProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList {
allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.PeriodSeconds), fldPath.Child("periodSeconds"))...)
allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.SuccessThreshold), fldPath.Child("successThreshold"))...)
allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.FailureThreshold), fldPath.Child("failureThreshold"))...)
if probe.TerminationGracePeriodSeconds != nil && *probe.TerminationGracePeriodSeconds <= 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), *probe.TerminationGracePeriodSeconds, "must be greater than 0"))
}
return allErrs
}
@ -2962,10 +3073,14 @@ const (
// restrictions in Linux libc name resolution handling.
// Max number of DNS name servers.
MaxDNSNameservers = 3
// Max number of domains in search path.
MaxDNSSearchPaths = 6
// Max number of characters in search path.
MaxDNSSearchListChars = 256
// Expanded max number of domains in the search path list.
MaxDNSSearchPathsExpanded = 32
// Expanded max number of characters in the search path.
MaxDNSSearchListCharsExpanded = 2048
// Max number of domains in the search path list.
MaxDNSSearchPathsLegacy = 6
// Max number of characters in the search path list.
MaxDNSSearchListCharsLegacy = 256
)
func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *field.Path) field.ErrorList {
@ -2978,7 +3093,7 @@ func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *fie
return allErrs
}
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path) field.ErrorList {
func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
// Validate DNSNone case. Must provide at least one DNS name server.
@ -3002,12 +3117,16 @@ func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolic
}
}
// Validate searches.
if len(dnsConfig.Searches) > MaxDNSSearchPaths {
allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v search paths", MaxDNSSearchPaths)))
maxDNSSearchPaths, maxDNSSearchListChars := MaxDNSSearchPathsLegacy, MaxDNSSearchListCharsLegacy
if opts.AllowExpandedDNSConfig {
maxDNSSearchPaths, maxDNSSearchListChars = MaxDNSSearchPathsExpanded, MaxDNSSearchListCharsExpanded
}
if len(dnsConfig.Searches) > maxDNSSearchPaths {
allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v search paths", maxDNSSearchPaths)))
}
// Include the space between search paths.
if len(strings.Join(dnsConfig.Searches, " ")) > MaxDNSSearchListChars {
allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, "must not have more than 256 characters (including spaces) in the search list"))
if len(strings.Join(dnsConfig.Searches, " ")) > maxDNSSearchListChars {
allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v characters (including spaces) in the search list", maxDNSSearchListChars)))
}
for i, search := range dnsConfig.Searches {
// it is fine to have a trailing dot
@ -3197,31 +3316,16 @@ func validateContainersOnlyForPod(containers []core.Container, fldPath *field.Pa
// PodValidationOptions contains the different settings for pod validation
type PodValidationOptions struct {
// Allow pod spec to have more than one huge page resource (with different sizes)
AllowMultipleHugePageResources bool
// Allow pod spec to use hugepages in downward API
AllowDownwardAPIHugePages bool
// Allow invalid pod-deletion-cost annotation value for backward compatibility.
AllowInvalidPodDeletionCost bool
// Allow pod spec to use non-integer multiple of huge page unit size
AllowIndivisibleHugePagesValues bool
}
// ValidatePodSingleHugePageResources checks if there are multiple huge
// pages resources in the pod object.
func ValidatePodSingleHugePageResources(pod *core.Pod, specPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
resourceSet := helper.ToPodResourcesSet(&pod.Spec)
hugePageResources := sets.NewString()
for resourceStr := range resourceSet {
if v1helper.IsHugePageResourceName(v1.ResourceName(resourceStr)) {
hugePageResources.Insert(resourceStr)
}
}
if len(hugePageResources) > 1 {
allErrs = append(allErrs, field.Invalid(specPath, hugePageResources.List(), "must use a single hugepage size in a pod spec"))
}
return allErrs
// Allow hostProcess field to be set in windows security context
AllowWindowsHostProcessField bool
// Allow more DNSSearchPaths and longer DNSSearchListChars
AllowExpandedDNSConfig bool
}
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,
@ -3253,10 +3357,6 @@ func validatePodMetadataAndSpec(pod *core.Pod, opts PodValidationOptions) field.
allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...)
allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...)
if !opts.AllowMultipleHugePageResources {
allErrs = append(allErrs, ValidatePodSingleHugePageResources(pod, specPath)...)
}
return allErrs
}
@ -3325,9 +3425,10 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"))...)
allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...)
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"))...)
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
allErrs = append(allErrs, validateTopologySpreadConstraints(spec.TopologySpreadConstraints, fldPath.Child("topologySpreadConstraints"))...)
allErrs = append(allErrs, validateWindowsHostProcessPod(spec, fldPath, opts)...)
if len(spec.ServiceAccountName) > 0 {
for _, msg := range ValidateServiceAccountName(spec.ServiceAccountName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceAccountName"), spec.ServiceAccountName, msg))
@ -3953,14 +4054,11 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...)
specPath := field.NewPath("spec")
if !opts.AllowMultipleHugePageResources {
allErrs = append(allErrs, ValidatePodSingleHugePageResources(newPod, specPath)...)
}
// validate updateable fields:
// 1. spec.containers[*].image
// 2. spec.initContainers[*].image
// 3. spec.activeDeadlineSeconds
// 4. spec.terminationGracePeriodSeconds
containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers"))
allErrs = append(allErrs, containerErrs...)
@ -4027,11 +4125,17 @@ func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) fiel
// tolerations are checked before the deep copy, so munge those too
mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone
// Relax validation of immutable fields to allow it to be set to 1 if it was previously negative.
if oldPod.Spec.TerminationGracePeriodSeconds != nil && *oldPod.Spec.TerminationGracePeriodSeconds < 0 &&
mungedPodSpec.TerminationGracePeriodSeconds != nil && *mungedPodSpec.TerminationGracePeriodSeconds == 1 {
mungedPodSpec.TerminationGracePeriodSeconds = oldPod.Spec.TerminationGracePeriodSeconds // +k8s:verify-mutation:reason=clone
}
if !apiequality.Semantic.DeepEqual(mungedPodSpec, oldPod.Spec) {
// This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is".
//TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff
specDiff := diff.ObjectDiff(mungedPodSpec, oldPod.Spec)
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)\n%v", specDiff)))
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds`, `spec.tolerations` (only additions to existing tolerations) or `spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)\n%v", specDiff)))
}
return allErrs
@ -4086,11 +4190,7 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions
allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.InitContainerStatuses, oldPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), oldPod.Spec.RestartPolicy)...)
if newIPErrs := validatePodIPs(newPod); len(newIPErrs) > 0 {
// Tolerate IP errors if IP errors already existed in the old pod. See http://issue.k8s.io/90625
// TODO(liggitt): Drop the check of oldPod in 1.20
if oldIPErrs := validatePodIPs(oldPod); len(oldIPErrs) == 0 {
allErrs = append(allErrs, newIPErrs...)
}
allErrs = append(allErrs, newIPErrs...)
}
return allErrs
@ -4319,35 +4419,6 @@ func ValidateService(service *core.Service) field.ErrorList {
ports[key] = true
}
// Validate TopologyKeys
if len(service.Spec.TopologyKeys) > 0 {
topoPath := specPath.Child("topologyKeys")
// topologyKeys is mutually exclusive with 'externalTrafficPolicy=Local'
if service.Spec.ExternalTrafficPolicy == core.ServiceExternalTrafficPolicyTypeLocal {
allErrs = append(allErrs, field.Forbidden(topoPath, "may not be specified when `externalTrafficPolicy=Local`"))
}
if len(service.Spec.TopologyKeys) > core.MaxServiceTopologyKeys {
allErrs = append(allErrs, field.TooMany(topoPath, len(service.Spec.TopologyKeys), core.MaxServiceTopologyKeys))
}
topoKeys := sets.NewString()
for i, key := range service.Spec.TopologyKeys {
keyPath := topoPath.Index(i)
if topoKeys.Has(key) {
allErrs = append(allErrs, field.Duplicate(keyPath, key))
}
topoKeys.Insert(key)
// "Any" must be the last value specified
if key == v1.TopologyKeyAny && i != len(service.Spec.TopologyKeys)-1 {
allErrs = append(allErrs, field.Invalid(keyPath, key, `"*" must be the last value specified`))
}
if key != v1.TopologyKeyAny {
for _, msg := range validation.IsQualifiedName(key) {
allErrs = append(allErrs, field.Invalid(keyPath, service.Spec.TopologyKeys, msg))
}
}
}
}
// Validate SourceRange field and annotation
_, ok := service.Annotations[core.AnnotationLoadBalancerSourceRangesKey]
if len(service.Spec.LoadBalancerSourceRanges) > 0 || ok {
@ -5408,7 +5479,7 @@ func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPa
}
if !limContainsCPUOrMemory && !reqContainsCPUOrMemory && (reqContainsHugePages || limContainsHugePages) {
allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("HugePages require cpu or memory")))
allErrs = append(allErrs, field.Forbidden(fldPath, "HugePages require cpu or memory"))
}
return allErrs
@ -5775,16 +5846,16 @@ func ValidateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList
return allErrs
}
if ip.IsUnspecified() {
allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be unspecified (0.0.0.0)"))
allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, fmt.Sprintf("may not be unspecified (%v)", ipAddress)))
}
if ip.IsLoopback() {
allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the loopback range (127.0.0.0/8)"))
allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the loopback range (127.0.0.0/8, ::1/128)"))
}
if ip.IsLinkLocalUnicast() {
allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local range (169.254.0.0/16)"))
allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local range (169.254.0.0/16, fe80::/10)"))
}
if ip.IsLinkLocalMulticast() {
allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local multicast range (224.0.0.0/24)"))
allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local multicast range (224.0.0.0/24, ff02::/10)"))
}
return allErrs
}
@ -5927,10 +5998,10 @@ func validateWindowsSecurityContextOptions(windowsOptions *core.WindowsSecurityC
if l := len(*windowsOptions.RunAsUserName); l == 0 {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, "runAsUserName cannot be an empty string"))
} else if ctrlRegex.MatchString(*windowsOptions.RunAsUserName) {
errMsg := fmt.Sprintf("runAsUserName cannot contain control characters")
errMsg := "runAsUserName cannot contain control characters"
allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
} else if parts := strings.Split(*windowsOptions.RunAsUserName, "\\"); len(parts) > 2 {
errMsg := fmt.Sprintf("runAsUserName cannot contain more than one backslash")
errMsg := "runAsUserName cannot contain more than one backslash"
allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
} else {
var (
@ -5957,7 +6028,7 @@ func validateWindowsSecurityContextOptions(windowsOptions *core.WindowsSecurityC
}
if l := len(user); l == 0 {
errMsg := fmt.Sprintf("runAsUserName's User cannot be empty")
errMsg := "runAsUserName's User cannot be empty"
allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg))
} else if l > maxRunAsUserNameUserLength {
errMsg := fmt.Sprintf("runAsUserName's User length must not be longer than %d characters", maxRunAsUserNameUserLength)
@ -5979,6 +6050,91 @@ func validateWindowsSecurityContextOptions(windowsOptions *core.WindowsSecurityC
return allErrs
}
func validateWindowsHostProcessPod(podSpec *core.PodSpec, fieldPath *field.Path, opts PodValidationOptions) field.ErrorList {
allErrs := field.ErrorList{}
// Keep track of container and hostProcess container count for validate
containerCount := 0
hostProcessContainerCount := 0
var podHostProcess *bool
if podSpec.SecurityContext != nil && podSpec.SecurityContext.WindowsOptions != nil {
podHostProcess = podSpec.SecurityContext.WindowsOptions.HostProcess
}
if !opts.AllowWindowsHostProcessField && podHostProcess != nil {
// Do not allow pods to persist data that sets hostProcess (true or false)
errMsg := "not allowed when feature gate 'WindowsHostProcessContainers' is not enabled"
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("securityContext", "windowsOptions", "hostProcess"), errMsg))
return allErrs
}
hostNetwork := false
if podSpec.SecurityContext != nil {
hostNetwork = podSpec.SecurityContext.HostNetwork
}
podshelper.VisitContainersWithPath(podSpec, fieldPath, func(c *core.Container, cFieldPath *field.Path) bool {
containerCount++
var containerHostProcess *bool = nil
if c.SecurityContext != nil && c.SecurityContext.WindowsOptions != nil {
containerHostProcess = c.SecurityContext.WindowsOptions.HostProcess
}
if !opts.AllowWindowsHostProcessField && containerHostProcess != nil {
// Do not allow pods to persist data that sets hostProcess (true or false)
errMsg := "not allowed when feature gate 'WindowsHostProcessContainers' is not enabled"
allErrs = append(allErrs, field.Forbidden(cFieldPath.Child("securityContext", "windowsOptions", "hostProcess"), errMsg))
}
if podHostProcess != nil && containerHostProcess != nil && *podHostProcess != *containerHostProcess {
errMsg := fmt.Sprintf("pod hostProcess value must be identical if both are specified, was %v", *podHostProcess)
allErrs = append(allErrs, field.Invalid(cFieldPath.Child("securityContext", "windowsOptions", "hostProcess"), *containerHostProcess, errMsg))
}
switch {
case containerHostProcess != nil && *containerHostProcess:
// Container explitly sets hostProcess=true
hostProcessContainerCount++
case containerHostProcess == nil && podHostProcess != nil && *podHostProcess:
// Container inherits hostProcess=true from pod settings
hostProcessContainerCount++
}
return true
})
if hostProcessContainerCount > 0 {
// Fail Pod validation if feature is not enabled (unless podspec already exists and contains HostProcess fields) instead of dropping fields based on PRR reivew.
if !opts.AllowWindowsHostProcessField {
errMsg := "pod must not contain Windows hostProcess containers when feature gate 'WindowsHostProcessContainers' is not enabled"
allErrs = append(allErrs, field.Forbidden(fieldPath, errMsg))
return allErrs
}
// At present, if a Windows Pods contains any HostProcess containers than all containers must be
// HostProcess containers (explicitly set or inherited).
if hostProcessContainerCount != containerCount {
errMsg := "If pod contains any hostProcess containers then all containers must be HostProcess containers"
allErrs = append(allErrs, field.Invalid(fieldPath, "", errMsg))
}
// At present Windows Pods which contain HostProcess containers must also set HostNetwork.
if hostNetwork != true {
errMsg := "hostNetwork must be true if pod contains any hostProcess containers"
allErrs = append(allErrs, field.Invalid(fieldPath.Child("hostNetwork"), hostNetwork, errMsg))
}
if !capabilities.Get().AllowPrivileged {
errMsg := "hostProcess containers are disallowed by cluster policy"
allErrs = append(allErrs, field.Forbidden(fieldPath, errMsg))
}
}
return allErrs
}
func ValidatePodLogOptions(opts *core.PodLogOptions) field.ErrorList {
allErrs := field.ErrorList{}
if opts.TailLines != nil && *opts.TailLines < 0 {

View File

@ -1400,39 +1400,6 @@ func (in *EphemeralContainerCommon) DeepCopy() *EphemeralContainerCommon {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EphemeralContainers) DeepCopyInto(out *EphemeralContainers) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.EphemeralContainers != nil {
in, out := &in.EphemeralContainers, &out.EphemeralContainers
*out = make([]EphemeralContainer, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EphemeralContainers.
func (in *EphemeralContainers) DeepCopy() *EphemeralContainers {
if in == nil {
return nil
}
out := new(EphemeralContainers)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *EphemeralContainers) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EphemeralVolumeSource) DeepCopyInto(out *EphemeralVolumeSource) {
*out = *in
@ -2969,6 +2936,11 @@ func (in *PersistentVolumeClaimSpec) DeepCopyInto(out *PersistentVolumeClaimSpec
*out = new(TypedLocalObjectReference)
(*in).DeepCopyInto(*out)
}
if in.DataSourceRef != nil {
in, out := &in.DataSourceRef, &out.DataSourceRef
*out = new(TypedLocalObjectReference)
(*in).DeepCopyInto(*out)
}
return
}
@ -5325,11 +5297,6 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.TopologyKeys != nil {
in, out := &in.TopologyKeys, &out.TopologyKeys
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AllocateLoadBalancerNodePorts != nil {
in, out := &in.AllocateLoadBalancerNodePorts, &out.AllocateLoadBalancerNodePorts
*out = new(bool)
@ -5928,6 +5895,11 @@ func (in *WindowsSecurityContextOptions) DeepCopyInto(out *WindowsSecurityContex
*out = new(string)
**out = **in
}
if in.HostProcess != nil {
in, out := &in.HostProcess, &out.HostProcess
*out = new(bool)
**out = **in
}
return
}

View File

@ -11,7 +11,6 @@ reviewers:
- caesarxuchao
- mikedanese
- liggitt
- erictune
- pmorie
- sttts
- saad-ali
@ -25,7 +24,6 @@ reviewers:
- dims
- errordeveloper
- rootfs
- mml
- resouer
- therc
- pweil-

View File

@ -149,8 +149,8 @@ type NetworkPolicyPort struct {
// should be allowed by the policy. This field cannot be defined if the port field
// is not defined or if the port field is defined as a named (string) port.
// The endPort must be equal or greater than port.
// This feature is in Alpha state and should be enabled using the Feature Gate
// "NetworkPolicyEndPort".
// This feature is in Beta state and is enabled by default.
// It can be disabled using the Feature Gate "NetworkPolicyEndPort".
// +optional
EndPort *int32
}
@ -492,8 +492,8 @@ const (
type HTTPIngressPath struct {
// Path is matched against the path of an incoming request. Currently it can
// contain characters disallowed from the conventional "path" part of a URL
// as defined by RFC 3986. Paths must begin with a '/'. When unspecified,
// all paths from incoming requests are matched.
// as defined by RFC 3986. Paths must begin with a '/' and must be present
// when using PathType with value "Exact" or "Prefix".
// +optional
Path string

View File

@ -2,6 +2,7 @@
# approval on api packages bubbles to api-approvers
reviewers:
- sig-apps-api-reviewers
- sig-apps-api-approvers
- sig-auth-policy-approvers
- sig-auth-policy-reviewers

View File

@ -25,10 +25,6 @@ const (
// KubeletPort is the default port for the kubelet server on each host machine.
// May be overridden by a flag at startup.
KubeletPort = 10250
// InsecureKubeControllerManagerPort is the default port for the controller manager status server.
// May be overridden by a flag at startup.
// Deprecated: use the secure KubeControllerManagerPort instead.
InsecureKubeControllerManagerPort = 10252
// KubeletReadOnlyPort exposes basic read-only services from the kubelet.
// May be overridden by a flag at startup.
// This is necessary for heapster to collect monitoring stats from the kubelet

View File

@ -124,6 +124,7 @@ type PodControllerRefManager struct {
BaseControllerRefManager
controllerKind schema.GroupVersionKind
podControl PodControlInterface
finalizers []string
}
// NewPodControllerRefManager returns a PodControllerRefManager that exposes
@ -143,6 +144,7 @@ func NewPodControllerRefManager(
selector labels.Selector,
controllerKind schema.GroupVersionKind,
canAdopt func() error,
finalizers ...string,
) *PodControllerRefManager {
return &PodControllerRefManager{
BaseControllerRefManager: BaseControllerRefManager{
@ -152,6 +154,7 @@ func NewPodControllerRefManager(
},
controllerKind: controllerKind,
podControl: podControl,
finalizers: finalizers,
}
}
@ -216,7 +219,7 @@ func (m *PodControllerRefManager) AdoptPod(pod *v1.Pod) error {
// Note that ValidateOwnerReferences() will reject this patch if another
// OwnerReference exists with controller=true.
patchBytes, err := ownerRefControllerPatch(m.Controller, m.controllerKind, pod.UID)
patchBytes, err := ownerRefControllerPatch(m.Controller, m.controllerKind, pod.UID, m.finalizers...)
if err != nil {
return err
}
@ -228,7 +231,7 @@ func (m *PodControllerRefManager) AdoptPod(pod *v1.Pod) error {
func (m *PodControllerRefManager) ReleasePod(pod *v1.Pod) error {
klog.V(2).Infof("patching pod %s_%s to remove its controllerRef to %s/%s:%s",
pod.Namespace, pod.Name, m.controllerKind.GroupVersion(), m.controllerKind.Kind, m.Controller.GetName())
patchBytes, err := deleteOwnerRefStrategicMergePatch(pod.UID, m.Controller.GetUID())
patchBytes, err := deleteOwnerRefStrategicMergePatch(pod.UID, m.Controller.GetUID(), m.finalizers...)
if err != nil {
return err
}
@ -518,19 +521,22 @@ type objectForDeleteOwnerRefStrategicMergePatch struct {
}
type objectMetaForMergePatch struct {
UID types.UID `json:"uid"`
OwnerReferences []map[string]string `json:"ownerReferences"`
UID types.UID `json:"uid"`
OwnerReferences []map[string]string `json:"ownerReferences"`
DeleteFinalizers []string `json:"$deleteFromPrimitiveList/finalizers,omitempty"`
}
func deleteOwnerRefStrategicMergePatch(dependentUID types.UID, ownerUIDs ...types.UID) ([]byte, error) {
var pieces []map[string]string
for _, ownerUID := range ownerUIDs {
pieces = append(pieces, map[string]string{"$patch": "delete", "uid": string(ownerUID)})
}
func deleteOwnerRefStrategicMergePatch(dependentUID types.UID, ownerUID types.UID, finalizers ...string) ([]byte, error) {
patch := objectForDeleteOwnerRefStrategicMergePatch{
Metadata: objectMetaForMergePatch{
UID: dependentUID,
OwnerReferences: pieces,
UID: dependentUID,
OwnerReferences: []map[string]string{
{
"$patch": "delete",
"uid": string(ownerUID),
},
},
DeleteFinalizers: finalizers,
},
}
patchBytes, err := json.Marshal(&patch)
@ -547,9 +553,10 @@ type objectForAddOwnerRefPatch struct {
type objectMetaForPatch struct {
OwnerReferences []metav1.OwnerReference `json:"ownerReferences"`
UID types.UID `json:"uid"`
Finalizers []string `json:"finalizers,omitempty"`
}
func ownerRefControllerPatch(controller metav1.Object, controllerKind schema.GroupVersionKind, uid types.UID) ([]byte, error) {
func ownerRefControllerPatch(controller metav1.Object, controllerKind schema.GroupVersionKind, uid types.UID, finalizers ...string) ([]byte, error) {
blockOwnerDeletion := true
isController := true
addControllerPatch := objectForAddOwnerRefPatch{
@ -565,6 +572,7 @@ func ownerRefControllerPatch(controller metav1.Object, controllerKind schema.Gro
BlockOwnerDeletion: &blockOwnerDeletion,
},
},
Finalizers: finalizers,
},
}
patchBytes, err := json.Marshal(&addControllerPatch)

View File

@ -445,13 +445,10 @@ func (r RealControllerRevisionControl) PatchControllerRevision(namespace, name s
// PodControlInterface is an interface that knows how to add or delete pods
// created as an interface to allow testing.
type PodControlInterface interface {
// CreatePods creates new pods according to the spec.
CreatePods(namespace string, template *v1.PodTemplateSpec, object runtime.Object) error
// CreatePodsOnNode creates a new pod according to the spec on the specified node,
// and sets the ControllerRef.
CreatePodsOnNode(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error
// CreatePodsWithControllerRef creates new pods according to the spec, and sets object as the pod's controller.
CreatePodsWithControllerRef(namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error
// CreatePods creates new pods according to the spec, and sets object as the pod's controller.
CreatePods(namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error
// CreatePodsWithGenerateName creates new pods according to the spec, sets object as the pod's controller and sets pod's generateName.
CreatePodsWithGenerateName(namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference, generateName string) error
// DeletePod deletes the pod identified by podID.
DeletePod(namespace string, podID string, object runtime.Object) error
// PatchPod patches the pod.
@ -516,22 +513,22 @@ func validateControllerRef(controllerRef *metav1.OwnerReference) error {
return nil
}
func (r RealPodControl) CreatePods(namespace string, template *v1.PodTemplateSpec, object runtime.Object) error {
return r.createPods("", namespace, template, object, nil)
func (r RealPodControl) CreatePods(namespace string, template *v1.PodTemplateSpec, controllerObject runtime.Object, controllerRef *metav1.OwnerReference) error {
return r.CreatePodsWithGenerateName(namespace, template, controllerObject, controllerRef, "")
}
func (r RealPodControl) CreatePodsWithControllerRef(namespace string, template *v1.PodTemplateSpec, controllerObject runtime.Object, controllerRef *metav1.OwnerReference) error {
func (r RealPodControl) CreatePodsWithGenerateName(namespace string, template *v1.PodTemplateSpec, controllerObject runtime.Object, controllerRef *metav1.OwnerReference, generateName string) error {
if err := validateControllerRef(controllerRef); err != nil {
return err
}
return r.createPods("", namespace, template, controllerObject, controllerRef)
}
func (r RealPodControl) CreatePodsOnNode(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error {
if err := validateControllerRef(controllerRef); err != nil {
pod, err := GetPodFromTemplate(template, controllerObject, controllerRef)
if err != nil {
return err
}
return r.createPods(nodeName, namespace, template, object, controllerRef)
if len(generateName) > 0 {
pod.ObjectMeta.GenerateName = generateName
}
return r.createPods(namespace, pod, controllerObject)
}
func (r RealPodControl) PatchPod(namespace, name string, data []byte) error {
@ -564,14 +561,7 @@ func GetPodFromTemplate(template *v1.PodTemplateSpec, parentObject runtime.Objec
return pod, nil
}
func (r RealPodControl) createPods(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error {
pod, err := GetPodFromTemplate(template, object, controllerRef)
if err != nil {
return err
}
if len(nodeName) != 0 {
pod.Spec.NodeName = nodeName
}
func (r RealPodControl) createPods(namespace string, pod *v1.Pod, object runtime.Object) error {
if len(labels.Set(pod.Labels)) == 0 {
return fmt.Errorf("unable to create pods, no labels")
}
@ -636,21 +626,7 @@ func (f *FakePodControl) PatchPod(namespace, name string, data []byte) error {
return nil
}
func (f *FakePodControl) CreatePods(namespace string, spec *v1.PodTemplateSpec, object runtime.Object) error {
f.Lock()
defer f.Unlock()
f.CreateCallCount++
if f.CreateLimit != 0 && f.CreateCallCount > f.CreateLimit {
return fmt.Errorf("not creating pod, limit %d already reached (create call %d)", f.CreateLimit, f.CreateCallCount)
}
f.Templates = append(f.Templates, *spec)
if f.Err != nil {
return f.Err
}
return nil
}
func (f *FakePodControl) CreatePodsWithControllerRef(namespace string, spec *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error {
func (f *FakePodControl) CreatePods(namespace string, spec *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error {
f.Lock()
defer f.Unlock()
f.CreateCallCount++
@ -665,14 +641,14 @@ func (f *FakePodControl) CreatePodsWithControllerRef(namespace string, spec *v1.
return nil
}
func (f *FakePodControl) CreatePodsOnNode(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error {
func (f *FakePodControl) CreatePodsWithGenerateName(namespace string, spec *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference, generateNamePrefix string) error {
f.Lock()
defer f.Unlock()
f.CreateCallCount++
if f.CreateLimit != 0 && f.CreateCallCount > f.CreateLimit {
return fmt.Errorf("not creating pod, limit %d already reached (create call %d)", f.CreateLimit, f.CreateCallCount)
}
f.Templates = append(f.Templates, *template)
f.Templates = append(f.Templates, *spec)
f.ControllerRefs = append(f.ControllerRefs, *controllerRef)
if f.Err != nil {
return f.Err

View File

@ -27,6 +27,7 @@ const (
// Every feature gate should add method here following this template:
//
// // owner: @username
// // kep: http://kep.k8s.io/NNN
// // alpha: v1.X
// MyFeature featuregate.Feature = "MyFeature"
@ -37,6 +38,7 @@ const (
// owner: @mtaufen
// alpha: v1.4
// beta: v1.11
// deprecated: 1.22
DynamicKubeletConfig featuregate.Feature = "DynamicKubeletConfig"
// owner: @pweil-
@ -124,7 +126,8 @@ const (
TopologyManager featuregate.Feature = "TopologyManager"
// owner: @cynepco3hahue(alukiano) @cezaryzukowski @k-wiatrzyk
// alpha:: v1.20
// alpha: v1.21
// beta: v1.22
// Allows setting memory affinity for a container based on NUMA topology
MemoryManager featuregate.Feature = "MemoryManager"
@ -137,31 +140,6 @@ const (
// Enable pods to set sysctls on a pod
Sysctls featuregate.Feature = "Sysctls"
// owner @smarterclayton
// alpha: v1.16
// beta: v1.19
// ga: v1.21
//
// Enable legacy behavior to vary cluster functionality on the node-role.kubernetes.io labels. On by default (legacy), will be turned off in 1.18.
// Lock to false in v1.21 and remove in v1.22.
LegacyNodeRoleBehavior featuregate.Feature = "LegacyNodeRoleBehavior"
// owner @brendandburns
// alpha: v1.9
// beta: v1.19
// ga: v1.21
//
// Enable nodes to exclude themselves from service load balancers
ServiceNodeExclusion featuregate.Feature = "ServiceNodeExclusion"
// owner @smarterclayton
// alpha: v1.16
// beta: v1.19
// ga: v1.21
//
// Enable nodes to exclude themselves from network disruption checks
NodeDisruptionExclusion featuregate.Feature = "NodeDisruptionExclusion"
// owner: @pospispa
// GA: v1.11
//
@ -179,6 +157,7 @@ const (
// owner: @mikedanese
// alpha: v1.13
// beta: v1.21
// ga: v1.22
//
// Migrate ServiceAccount volumes to use a projected volume consisting of a
// ServiceAccountTokenVolumeProjection. This feature adds new required flags
@ -196,20 +175,6 @@ const (
// intended to be used for service account token verification.
ServiceAccountIssuerDiscovery featuregate.Feature = "ServiceAccountIssuerDiscovery"
// owner: @Random-Liu
// beta: v1.11
// ga: v1.21
//
// Enable container log rotation for cri container runtime
CRIContainerLogRotation featuregate.Feature = "CRIContainerLogRotation"
// owner: @krmayankk
// beta: v1.14
// ga: v1.21
//
// Enables control over the primary group ID of containers' init processes.
RunAsGroup featuregate.Feature = "RunAsGroup"
// owner: @saad-ali
// ga: v1.10
//
@ -217,14 +182,6 @@ const (
// Do not remove this feature gate even though it's GA
VolumeSubpath featuregate.Feature = "VolumeSubpath"
// owner: @ravig
// alpha: v1.11
//
// Include volume count on node to be considered for balanced resource allocation while scheduling.
// A node which has closer cpu,memory utilization and volume count is favoured by scheduler
// while making decisions.
BalanceAttachedNodeVolumes featuregate.Feature = "BalanceAttachedNodeVolumes"
// owner: @pohly
// alpha: v1.14
// beta: v1.16
@ -255,6 +212,7 @@ const (
// owner: @chendave
// alpha: v1.21
// beta: v1.22
//
// PreferNominatedNode tells scheduler whether the nominated node will be checked first before looping
// all the rest of nodes in the cluster.
@ -280,28 +238,14 @@ const (
// (Kube) Node Lifecycle Controller uses these heartbeats as a node health signal.
NodeLease featuregate.Feature = "NodeLease"
// owner: @janosi
// alpha: v1.12
// beta: v1.19
// GA: v1.20
//
// Enables SCTP as new protocol for Service ports, NetworkPolicy, and ContainerPort in Pod/Containers definition
SCTPSupport featuregate.Feature = "SCTPSupport"
// owner: @rikatz
// kep: http://kep.k8s.io/2079
// alpha: v1.21
// beta: v1.22
//
// Enables the endPort field in NetworkPolicy to enable a Port Range behavior in Network Policies.
NetworkPolicyEndPort featuregate.Feature = "NetworkPolicyEndPort"
// owner: @xing-yang
// alpha: v1.12
// beta: v1.17
// GA: v1.20
//
// Enable volume snapshot data source support.
VolumeSnapshotDataSource featuregate.Feature = "VolumeSnapshotDataSource"
// owner: @jessfraz
// alpha: v1.12
//
@ -316,10 +260,20 @@ const (
// owner: @alculquicondor
// alpha: v1.21
// beta: v1.22
//
// Allows Job controller to manage Pod completions per completion index.
IndexedJob featuregate.Feature = "IndexedJob"
// owner: @alculquicondor
// alpha: v1.22
//
// Track Job completion without relying on Pod remaining in the cluster
// indefinitely. Pod finalizers, in addition to a field in the Job status
// allow the Job controller to keep track of Pods that it didn't account for
// yet.
JobTrackingWithFinalizers featuregate.Feature = "JobTrackingWithFinalizers"
// owner: @dashpole
// alpha: v1.13
// beta: v1.15
@ -392,13 +346,6 @@ const (
// Enables the vSphere in-tree driver to vSphere CSI Driver migration feature.
CSIMigrationvSphere featuregate.Feature = "CSIMigrationvSphere"
// owner: @divyenpatel
// beta: v1.19 (requires: vSphere vCenter/ESXi Version: 7.0u1, HW Version: VM version 15)
//
// Disables the vSphere in-tree driver.
// Expects vSphere CSI Driver to be installed and configured on all nodes.
CSIMigrationvSphereComplete featuregate.Feature = "CSIMigrationvSphereComplete"
// owner: @divyenpatel
// alpha: v1.21
//
@ -432,6 +379,13 @@ const (
// a volume in a Pod.
ConfigurableFSGroupPolicy featuregate.Feature = "ConfigurableFSGroupPolicy"
// owner: @gnufied, @verult
// alpha: v1.22
// If supported by the CSI driver, delegates the role of applying FSGroup to
// the driver by passing FSGroup through the NodeStageVolume and
// NodePublishVolume calls.
DelegateFSGroupToCSIDriver featuregate.Feature = "DelegateFSGroupToCSIDriver"
// owner: @RobertKrawitz, @derekwaynecarr
// beta: v1.15
// GA: v1.20
@ -461,6 +415,7 @@ const (
PodOverhead featuregate.Feature = "PodOverhead"
// owner: @khenidak
// kep: http://kep.k8s.io/563
// alpha: v1.15
// beta: v1.21
//
@ -468,6 +423,7 @@ const (
IPv6DualStack featuregate.Feature = "IPv6DualStack"
// owner: @robscott @freehan
// kep: http://kep.k8s.io/752
// alpha: v1.16
// beta: v1.18
// ga: v1.21
@ -476,15 +432,19 @@ const (
EndpointSlice featuregate.Feature = "EndpointSlice"
// owner: @robscott @freehan
// kep: http://kep.k8s.io/752
// alpha: v1.18
// beta: v1.19
// ga: v1.22
//
// Enable Endpoint Slice consumption by kube-proxy for improved scalability.
EndpointSliceProxying featuregate.Feature = "EndpointSliceProxying"
// owner: @robscott @kumarvin123
// kep: http://kep.k8s.io/752
// alpha: v1.19
// beta: v1.21
// ga: v1.22
//
// Enable Endpoint Slice consumption by kube-proxy in Windows for improved scalability.
WindowsEndpointSliceProxying featuregate.Feature = "WindowsEndpointSliceProxying"
@ -514,33 +474,18 @@ const (
// owner: @alaypatel07, @soltysh
// alpha: v1.20
// beta: v1.21
// GA: v1.22
//
// CronJobControllerV2 controls whether the controller manager starts old cronjob
// controller or new one which is implemented with informers and delaying queue
//
// This feature is deprecated, and will be removed in v1.22.
CronJobControllerV2 featuregate.Feature = "CronJobControllerV2"
// owner: @smarterclayton
// alpha: v1.21
//
// beta: v1.22
// DaemonSets allow workloads to maintain availability during update per node
DaemonSetUpdateSurge featuregate.Feature = "DaemonSetUpdateSurge"
// owner: @m1093782566
// alpha: v1.17
//
// Enables topology aware service routing
ServiceTopology featuregate.Feature = "ServiceTopology"
// owner: @robscott
// alpha: v1.18
// beta: v1.19
// ga: v1.20
//
// Enables AppProtocol field for Services and Endpoints.
ServiceAppProtocol featuregate.Feature = "ServiceAppProtocol"
// owner: @wojtek-t
// alpha: v1.18
// beta: v1.19
@ -552,6 +497,7 @@ const (
// owner: @bart0sh
// alpha: v1.18
// beta: v1.19
// GA: 1.22
//
// Enables usage of HugePages-<size> in a volume medium,
// e.g. emptyDir:
@ -565,13 +511,6 @@ const (
// Enables usage of hugepages-<size> in downward API.
DownwardAPIHugePages featuregate.Feature = "DownwardAPIHugePages"
// owner: @freehan
// GA: v1.18
//
// Enable ExternalTrafficPolicy for Service ExternalIPs.
// This is for bug fix #69811
ExternalPolicyForExternalIP featuregate.Feature = "ExternalPolicyForExternalIP"
// owner: @bswartz
// alpha: v1.18
//
@ -579,8 +518,10 @@ const (
AnyVolumeDataSource featuregate.Feature = "AnyVolumeDataSource"
// owner: @javidiaz
// kep: http://kep.k8s.io/1797
// alpha: v1.19
// beta: v1.20
// GA: v1.22
//
// Allow setting the Fully Qualified Domain Name (FQDN) in the hostname of a Pod. If a Pod does not
// have FQDN, this feature has no effect.
@ -613,22 +554,23 @@ const (
// in target pods
HPAContainerMetrics featuregate.Feature = "HPAContainerMetrics"
// owner: @zshihang
// alpha: v1.13
// beta: v1.20
// ga: v1.21
//
// Allows kube-controller-manager to publish kube-root-ca.crt configmap to
// every namespace. This feature is a prerequisite of BoundServiceAccountTokenVolume.
RootCAConfigMap featuregate.Feature = "RootCAConfigMap"
// owner: @andrewsykim
// kep: http://kep.k8s.io/1672
// alpha: v1.20
// beta: v1.22
//
// Enable Terminating condition in Endpoint Slices.
EndpointSliceTerminatingCondition featuregate.Feature = "EndpointSliceTerminatingCondition"
// owner: @andrewsykim
// kep: http://kep.k8s.io/1669
// alpha: v1.22
//
// Enable kube-proxy to handle terminating ednpoints when externalTrafficPolicy=Local
ProxyTerminatingEndpoints featuregate.Feature = "ProxyTerminatingEndpoints"
// owner: @robscott
// kep: http://kep.k8s.io/752
// alpha: v1.20
//
// Enable NodeName field on Endpoint Slices.
@ -636,6 +578,7 @@ const (
// owner: @derekwaynecarr
// alpha: v1.20
// beta: v1.22
//
// Enables kubelet support to size memory backed volumes
SizeMemoryBackedVolumes featuregate.Feature = "SizeMemoryBackedVolumes"
@ -654,9 +597,16 @@ const (
// Enable kubelet exec plugins for image pull credentials.
KubeletCredentialProviders featuregate.Feature = "KubeletCredentialProviders"
// owner: @andrewsykim
// alpha: v1.22
//
// Disable any functionality in kube-apiserver, kube-controller-manager and kubelet related to the `--cloud-provider` component flag.
DisableCloudProviders featuregate.Feature = "DisableCloudProviders"
// owner: @zshihang
// alpha: v1.20
// beta: v1.21
// ga: v1.22
//
// Enable kubelet to pass pod's service account token to NodePublishVolume
// call of CSI driver which is mounting volumes for that pod.
@ -669,12 +619,15 @@ const (
GracefulNodeShutdown featuregate.Feature = "GracefulNodeShutdown"
// owner: @andrewsykim @uablrek
// kep: http://kep.k8s.io/1864
// alpha: v1.20
// beta: v1.22
//
// Allows control if NodePorts shall be created for services with "type: LoadBalancer" by defining the spec.AllocateLoadBalancerNodePorts field (bool)
ServiceLBNodePortControl featuregate.Feature = "ServiceLBNodePortControl"
// owner: @janosi
// kep: http://kep.k8s.io/1435
// alpha: v1.20
//
// Enables the usage of different protocols in the same Service with type=LoadBalancer
@ -686,11 +639,13 @@ const (
// owner: @ahg-g
// alpha: v1.21
// beta: v1.22
//
// Enables controlling pod ranking on replicaset scale-down.
PodDeletionCost featuregate.Feature = "PodDeletionCost"
// owner: @robscott
// kep: http://kep.k8s.io/2433
// alpha: v1.21
//
// Enables topology aware hints for EndpointSlices
@ -702,31 +657,44 @@ const (
// Allows user to override pod-level terminationGracePeriod for probes
ProbeTerminationGracePeriod featuregate.Feature = "ProbeTerminationGracePeriod"
// owner: @ehashman
// alpha: v1.22
//
// Permits kubelet to run with swap enabled
NodeSwap featuregate.Feature = "NodeSwap"
// owner: @ahg-g
// alpha: v1.21
// beta: v1.22
//
// Allow specifying NamespaceSelector in PodAffinityTerm.
PodAffinityNamespaceSelector featuregate.Feature = "PodAffinityNamespaceSelector"
// owner: @andrewsykim @xudongliuharold
// owner: @andrewsykim @XudongLiuHarold
// kep: http://kep.k8s.io/1959
// alpha: v1.21
// beta: v1.22
//
// Enable support multiple Service "type: LoadBalancer" implementations in a cluster by specifying LoadBalancerClass
ServiceLoadBalancerClass featuregate.Feature = "ServiceLoadBalancerClass"
// owner: @damemi
// aplpha: v1.21
// alpha: v1.21
// beta: v1.22
//
// Enables scaling down replicas via logarithmic comparison of creation/ready timestamps
LogarithmicScaleDown featuregate.Feature = "LogarithmicScaleDown"
// owner: @hbagdi
// kep: http://kep.k8s.io/2365
// alpha: v1.21
// beta: v1.22
//
// Enable Scope and Namespace fields on IngressClassParametersReference.
IngressClassNamespacedParams featuregate.Feature = "IngressClassNamespacedParams"
// owner: @maplain @andrewsykim
// kep: http://kep.k8s.io/2086
// alpha: v1.21
//
// Enables node-local routing for Service internal traffic
@ -734,6 +702,7 @@ const (
// owner: @adtac
// alpha: v1.21
// beta: v1.22
//
// Allows jobs to be created in the suspended state.
SuspendJob featuregate.Feature = "SuspendJob"
@ -745,7 +714,9 @@ const (
KubeletPodResourcesGetAllocatable featuregate.Feature = "KubeletPodResourcesGetAllocatable"
// owner: @jayunit100 @abhiraut @rikatz
// kep: http://kep.k8s.io/2161
// beta: v1.21
// ga: v1.22
//
// Labels all namespaces with a default label "kubernetes.io/metadata.name: <namespaceName>"
NamespaceDefaultLabelName featuregate.Feature = "NamespaceDefaultLabelName"
@ -755,6 +726,77 @@ const (
//
// Enables kubelet to detect CSI volume condition and send the event of the abnormal volume to the corresponding pod that is using it.
CSIVolumeHealth featuregate.Feature = "CSIVolumeHealth"
// owner: @marosset
// alpha: v1.22
//
// Enables support for 'HostProcess' containers on Windows nodes.
WindowsHostProcessContainers featuregate.Feature = "WindowsHostProcessContainers"
// owner: @ravig
// alpha: v1.22
//
// StatefulSetMinReadySeconds allows minReadySeconds to be respected by StatefulSet controller
StatefulSetMinReadySeconds featuregate.Feature = "StatefulSetMinReadySeconds"
// owner: @gjkim42
// kep: http://kep.k8s.io/2595
// alpha: v1.22
//
// Enables apiserver and kubelet to allow up to 32 DNSSearchPaths and up to 2048 DNSSearchListChars.
ExpandedDNSConfig featuregate.Feature = "ExpandedDNSConfig"
// owner: @saschagrunert
// alpha: v1.22
//
// Enables the use of `RuntimeDefault` as the default seccomp profile for all workloads.
SeccompDefault featuregate.Feature = "SeccompDefault"
// owner: @liggitt, @tallclair, sig-auth
// alpha: v1.22
//
// Enables the PodSecurity admission plugin
PodSecurity featuregate.Feature = "PodSecurity"
// owner: @chrishenzie
// alpha: v1.22
//
// Enables usage of the ReadWriteOncePod PersistentVolume access mode.
ReadWriteOncePod featuregate.Feature = "ReadWriteOncePod"
// owner: @enj
// beta: v1.22
//
// Allows clients to request a duration for certificates issued via the Kubernetes CSR API.
CSRDuration featuregate.Feature = "CSRDuration"
// owner: @AkihiroSuda
// alpha: v1.22
//
// Enables support for running kubelet in a user namespace.
// The user namespace has to be created before running kubelet.
// All the node components such as CRI need to be running in the same user namespace.
KubeletInUserNamespace featuregate.Feature = "KubeletInUserNamespace"
// owner: @xiaoxubeii
// kep: http://kep.k8s.io/2570
// alpha: v1.22
//
// Enables kubelet to support memory QoS with cgroups v2.
MemoryQoS featuregate.Feature = "MemoryQoS"
// owner: @fromanirh
// alpha: v1.22
//
// Allow fine-tuning of cpumanager policies
CPUManagerPolicyOptions featuregate.Feature = "CPUManagerPolicyOptions"
// owner: @jiahuif
// alpha: v1.21
// beta: v1.22
//
// Enables Leader Migration for kube-controller-manager and cloud-controller-manager
ControllerManagerLeaderMigration featuregate.Feature = "ControllerManagerLeaderMigration"
)
func init() {
@ -766,7 +808,7 @@ func init() {
// available throughout Kubernetes binaries.
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
AppArmor: {Default: true, PreRelease: featuregate.Beta},
DynamicKubeletConfig: {Default: true, PreRelease: featuregate.Beta},
DynamicKubeletConfig: {Default: false, PreRelease: featuregate.Deprecated}, // feature gate is deprecated in 1.22, remove no early than 1.23
ExperimentalHostUserNamespaceDefaultingGate: {Default: false, PreRelease: featuregate.Beta},
DevicePlugins: {Default: true, PreRelease: featuregate.Beta},
RotateKubeletServerCertificate: {Default: true, PreRelease: featuregate.Beta},
@ -778,17 +820,14 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
ExpandInUsePersistentVolumes: {Default: true, PreRelease: featuregate.Beta},
ExpandCSIVolumes: {Default: true, PreRelease: featuregate.Beta},
CPUManager: {Default: true, PreRelease: featuregate.Beta},
MemoryManager: {Default: false, PreRelease: featuregate.Alpha},
MemoryManager: {Default: true, PreRelease: featuregate.Beta},
CPUCFSQuotaPeriod: {Default: false, PreRelease: featuregate.Alpha},
TopologyManager: {Default: true, PreRelease: featuregate.Beta},
ServiceNodeExclusion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
NodeDisruptionExclusion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
StorageObjectInUseProtection: {Default: true, PreRelease: featuregate.GA},
SupportPodPidsLimit: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
SupportNodePidsLimit: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
BoundServiceAccountTokenVolume: {Default: true, PreRelease: featuregate.Beta},
BoundServiceAccountTokenVolume: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
ServiceAccountIssuerDiscovery: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
CRIContainerLogRotation: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
CSIMigration: {Default: true, PreRelease: featuregate.Beta},
CSIMigrationGCE: {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires GCE PD CSI Driver)
InTreePluginGCEUnregister: {Default: false, PreRelease: featuregate.Alpha},
@ -799,91 +838,97 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
CSIMigrationAzureFile: {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires Azure File CSI driver)
InTreePluginAzureFileUnregister: {Default: false, PreRelease: featuregate.Alpha},
CSIMigrationvSphere: {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires vSphere CSI driver)
CSIMigrationvSphereComplete: {Default: false, PreRelease: featuregate.Beta}, // remove in 1.22
InTreePluginvSphereUnregister: {Default: false, PreRelease: featuregate.Alpha},
CSIMigrationOpenStack: {Default: true, PreRelease: featuregate.Beta},
InTreePluginOpenStackUnregister: {Default: false, PreRelease: featuregate.Alpha},
VolumeSubpath: {Default: true, PreRelease: featuregate.GA},
ConfigurableFSGroupPolicy: {Default: true, PreRelease: featuregate.Beta},
BalanceAttachedNodeVolumes: {Default: false, PreRelease: featuregate.Alpha},
CSIInlineVolume: {Default: true, PreRelease: featuregate.Beta},
CSIStorageCapacity: {Default: true, PreRelease: featuregate.Beta},
CSIServiceAccountToken: {Default: true, PreRelease: featuregate.Beta},
CSIServiceAccountToken: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
GenericEphemeralVolume: {Default: true, PreRelease: featuregate.Beta},
CSIVolumeFSGroupPolicy: {Default: true, PreRelease: featuregate.Beta},
RuntimeClass: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
NodeLease: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
SCTPSupport: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
NetworkPolicyEndPort: {Default: false, PreRelease: featuregate.Alpha},
VolumeSnapshotDataSource: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.21
NetworkPolicyEndPort: {Default: true, PreRelease: featuregate.Beta},
ProcMountType: {Default: false, PreRelease: featuregate.Alpha},
TTLAfterFinished: {Default: true, PreRelease: featuregate.Beta},
IndexedJob: {Default: false, PreRelease: featuregate.Alpha},
IndexedJob: {Default: true, PreRelease: featuregate.Beta},
JobTrackingWithFinalizers: {Default: false, PreRelease: featuregate.Alpha},
KubeletPodResources: {Default: true, PreRelease: featuregate.Beta},
LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha},
NonPreemptingPriority: {Default: true, PreRelease: featuregate.Beta},
PodOverhead: {Default: true, PreRelease: featuregate.Beta},
IPv6DualStack: {Default: true, PreRelease: featuregate.Beta},
EndpointSlice: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
EndpointSliceProxying: {Default: true, PreRelease: featuregate.Beta},
EndpointSliceTerminatingCondition: {Default: false, PreRelease: featuregate.Alpha},
EndpointSliceProxying: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
EndpointSliceTerminatingCondition: {Default: true, PreRelease: featuregate.Beta},
ProxyTerminatingEndpoints: {Default: false, PreRelease: featuregate.Alpha},
EndpointSliceNodeName: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, //remove in 1.25
WindowsEndpointSliceProxying: {Default: true, PreRelease: featuregate.Beta},
WindowsEndpointSliceProxying: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
StartupProbe: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
PodDisruptionBudget: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
CronJobControllerV2: {Default: true, PreRelease: featuregate.Beta},
DaemonSetUpdateSurge: {Default: false, PreRelease: featuregate.Alpha},
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
ServiceAppProtocol: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
CronJobControllerV2: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
DaemonSetUpdateSurge: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.22
ImmutableEphemeralVolumes: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.24
HugePageStorageMediumSize: {Default: true, PreRelease: featuregate.Beta},
HugePageStorageMediumSize: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
DownwardAPIHugePages: {Default: false, PreRelease: featuregate.Beta}, // on by default in 1.22
ExternalPolicyForExternalIP: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
AnyVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha},
DefaultPodTopologySpread: {Default: true, PreRelease: featuregate.Beta},
SetHostnameAsFQDN: {Default: true, PreRelease: featuregate.Beta},
SetHostnameAsFQDN: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, //remove in 1.24
WinOverlay: {Default: true, PreRelease: featuregate.Beta},
WinDSR: {Default: false, PreRelease: featuregate.Alpha},
DisableAcceleratorUsageMetrics: {Default: true, PreRelease: featuregate.Beta},
HPAContainerMetrics: {Default: false, PreRelease: featuregate.Alpha},
RootCAConfigMap: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
SizeMemoryBackedVolumes: {Default: false, PreRelease: featuregate.Alpha},
SizeMemoryBackedVolumes: {Default: true, PreRelease: featuregate.Beta},
ExecProbeTimeout: {Default: true, PreRelease: featuregate.GA}, // lock to default and remove after v1.22 based on KEP #1972 update
KubeletCredentialProviders: {Default: false, PreRelease: featuregate.Alpha},
GracefulNodeShutdown: {Default: true, PreRelease: featuregate.Beta},
ServiceLBNodePortControl: {Default: false, PreRelease: featuregate.Alpha},
ServiceLBNodePortControl: {Default: true, PreRelease: featuregate.Beta},
MixedProtocolLBService: {Default: false, PreRelease: featuregate.Alpha},
VolumeCapacityPriority: {Default: false, PreRelease: featuregate.Alpha},
PreferNominatedNode: {Default: false, PreRelease: featuregate.Alpha},
ProbeTerminationGracePeriod: {Default: false, PreRelease: featuregate.Alpha},
RunAsGroup: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
PodDeletionCost: {Default: false, PreRelease: featuregate.Alpha},
PreferNominatedNode: {Default: true, PreRelease: featuregate.Beta},
ProbeTerminationGracePeriod: {Default: false, PreRelease: featuregate.Beta}, // Default to false in beta 1.22, set to true in 1.24
NodeSwap: {Default: false, PreRelease: featuregate.Alpha},
PodDeletionCost: {Default: true, PreRelease: featuregate.Beta},
TopologyAwareHints: {Default: false, PreRelease: featuregate.Alpha},
PodAffinityNamespaceSelector: {Default: false, PreRelease: featuregate.Alpha},
ServiceLoadBalancerClass: {Default: false, PreRelease: featuregate.Alpha},
LogarithmicScaleDown: {Default: false, PreRelease: featuregate.Alpha},
IngressClassNamespacedParams: {Default: false, PreRelease: featuregate.Alpha},
ServiceInternalTrafficPolicy: {Default: false, PreRelease: featuregate.Alpha},
SuspendJob: {Default: false, PreRelease: featuregate.Alpha},
PodAffinityNamespaceSelector: {Default: true, PreRelease: featuregate.Beta},
ServiceLoadBalancerClass: {Default: true, PreRelease: featuregate.Beta},
IngressClassNamespacedParams: {Default: true, PreRelease: featuregate.Beta},
ServiceInternalTrafficPolicy: {Default: true, PreRelease: featuregate.Beta},
LogarithmicScaleDown: {Default: true, PreRelease: featuregate.Beta},
SuspendJob: {Default: true, PreRelease: featuregate.Beta},
KubeletPodResourcesGetAllocatable: {Default: false, PreRelease: featuregate.Alpha},
NamespaceDefaultLabelName: {Default: true, PreRelease: featuregate.Beta}, // graduate to GA and lock to default in 1.22, remove in 1.24
NamespaceDefaultLabelName: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.24
CSIVolumeHealth: {Default: false, PreRelease: featuregate.Alpha},
WindowsHostProcessContainers: {Default: false, PreRelease: featuregate.Alpha},
DisableCloudProviders: {Default: false, PreRelease: featuregate.Alpha},
StatefulSetMinReadySeconds: {Default: false, PreRelease: featuregate.Alpha},
ExpandedDNSConfig: {Default: false, PreRelease: featuregate.Alpha},
SeccompDefault: {Default: false, PreRelease: featuregate.Alpha},
PodSecurity: {Default: false, PreRelease: featuregate.Alpha},
ReadWriteOncePod: {Default: false, PreRelease: featuregate.Alpha},
CSRDuration: {Default: true, PreRelease: featuregate.Beta},
DelegateFSGroupToCSIDriver: {Default: false, PreRelease: featuregate.Alpha},
KubeletInUserNamespace: {Default: false, PreRelease: featuregate.Alpha},
MemoryQoS: {Default: false, PreRelease: featuregate.Alpha},
CPUManagerPolicyOptions: {Default: false, PreRelease: featuregate.Alpha},
ControllerManagerLeaderMigration: {Default: true, PreRelease: featuregate.Beta},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:
genericfeatures.StreamingProxyRedirects: {Default: true, PreRelease: featuregate.Deprecated},
genericfeatures.ValidateProxyRedirects: {Default: true, PreRelease: featuregate.Beta},
genericfeatures.StreamingProxyRedirects: {Default: false, PreRelease: featuregate.Deprecated}, // remove in 1.24
genericfeatures.ValidateProxyRedirects: {Default: true, PreRelease: featuregate.Deprecated},
genericfeatures.AdvancedAuditing: {Default: true, PreRelease: featuregate.GA},
genericfeatures.APIResponseCompression: {Default: true, PreRelease: featuregate.Beta},
genericfeatures.APIListChunking: {Default: true, PreRelease: featuregate.Beta},
genericfeatures.DryRun: {Default: true, PreRelease: featuregate.GA},
genericfeatures.ServerSideApply: {Default: true, PreRelease: featuregate.Beta},
genericfeatures.ServerSideApply: {Default: true, PreRelease: featuregate.GA},
genericfeatures.APIPriorityAndFairness: {Default: true, PreRelease: featuregate.Beta},
genericfeatures.WarningHeaders: {Default: true, PreRelease: featuregate.Beta},
genericfeatures.WarningHeaders: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.24
// features that enable backwards compatibility but are scheduled to be removed
// ...
HPAScaleToZero: {Default: false, PreRelease: featuregate.Alpha},
LegacyNodeRoleBehavior: {Default: false, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.22
HPAScaleToZero: {Default: false, PreRelease: featuregate.Alpha},
}

View File

@ -221,6 +221,10 @@ type KubeletConfiguration struct {
// CPUManagerPolicy is the name of the policy to use.
// Requires the CPUManager feature gate to be enabled.
CPUManagerPolicy string
// CPUManagerPolicyOptions is a set of key=value which allows to set extra options
// to fine tune the behaviour of the cpu manager policies.
// Requires both the "CPUManager" and "CPUManagerPolicyOptions" feature gates to be enabled.
CPUManagerPolicyOptions map[string]string
// CPU Manager reconciliation period.
// Requires the CPUManager feature gate to be enabled.
CPUManagerReconcilePeriod metav1.Duration
@ -326,6 +330,10 @@ type KubeletConfiguration struct {
FeatureGates map[string]bool
// Tells the Kubelet to fail to start if swap is enabled on the node.
FailSwapOn bool
// memorySwap configures swap memory available to container workloads.
// +featureGate=NodeSwap
// +optional
MemorySwap MemorySwapConfiguration
// A quantity defines the maximum size of the container log file before it is rotated. For example: "5Mi" or "256Ki".
ContainerLogMaxSize string
// Maximum number of container log files that can be present for a container.
@ -407,6 +415,17 @@ type KubeletConfiguration struct {
EnableProfilingHandler bool
// EnableDebugFlagsHandler enables/debug/flags/v handler.
EnableDebugFlagsHandler bool
// SeccompDefault enables the use of `RuntimeDefault` as the default seccomp profile for all workloads.
SeccompDefault bool
// MemoryThrottlingFactor specifies the factor multiplied by the memory limit or node allocatable memory
// when setting the cgroupv2 memory.high value to enforce MemoryQoS.
// Decreasing this factor will set lower high limit for container cgroups and put heavier reclaim pressure
// while increasing will put less reclaim pressure.
// See http://kep.k8s.io/2570 for more details.
// Default: 0.8
// +featureGate=MemoryQoS
// +optional
MemoryThrottlingFactor *float64
}
// KubeletAuthorizationMode denotes the authorization mode for the kubelet
@ -566,3 +585,12 @@ type MemoryReservation struct {
NumaNode int32
Limits v1.ResourceList
}
type MemorySwapConfiguration struct {
// swapBehavior configures swap memory available to container workloads. May be one of
// "", "LimitedSwap": workload combined memory and swap usage cannot exceed pod memory limit
// "UnlimitedSwap": workloads can use unlimited swap, up to the allocatable limit.
// +featureGate=NodeSwap
// +optional
SwapBehavior string
}

View File

@ -201,6 +201,13 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
out.NodeStatusReportFrequency = in.NodeStatusReportFrequency
out.ImageMinimumGCAge = in.ImageMinimumGCAge
out.VolumeStatsAggPeriod = in.VolumeStatsAggPeriod
if in.CPUManagerPolicyOptions != nil {
in, out := &in.CPUManagerPolicyOptions, &out.CPUManagerPolicyOptions
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod
if in.QOSReserved != nil {
in, out := &in.QOSReserved, &out.QOSReserved
@ -247,6 +254,7 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
(*out)[key] = val
}
}
out.MemorySwap = in.MemorySwap
if in.AllowedUnsafeSysctls != nil {
in, out := &in.AllowedUnsafeSysctls, &out.AllowedUnsafeSysctls
*out = make([]string, len(*in))
@ -281,6 +289,11 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.MemoryThrottlingFactor != nil {
in, out := &in.MemoryThrottlingFactor, &out.MemoryThrottlingFactor
*out = new(float64)
**out = **in
}
return
}
@ -376,6 +389,22 @@ func (in *MemoryReservation) DeepCopy() *MemoryReservation {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MemorySwapConfiguration) DeepCopyInto(out *MemorySwapConfiguration) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemorySwapConfiguration.
func (in *MemorySwapConfiguration) DeepCopy() *MemorySwapConfiguration {
if in == nil {
return nil
}
out := new(MemorySwapConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SerializedNodeConfigSource) DeepCopyInto(out *SerializedNodeConfigSource) {
*out = *in

View File

@ -38,3 +38,9 @@ const (
KubeReservedEnforcementKey = "kube-reserved"
NodeAllocatableNoneKey = "none"
)
// SwapBehavior types
const (
LimitedSwap = "LimitedSwap"
UnlimitedSwap = "UnlimitedSwap"
)

View File

@ -120,8 +120,8 @@ const (
SyncPodUpdate
// SyncPodCreate is when the pod is created from source
SyncPodCreate
// SyncPodKill is when the pod is killed based on a trigger internal to the kubelet for eviction.
// If a SyncPodKill request is made to pod workers, the request is never dropped, and will always be processed.
// SyncPodKill is when the pod should have no running containers. A pod stopped in this way could be
// restarted in the future due config changes.
SyncPodKill
)
@ -184,3 +184,8 @@ func Preemptable(preemptor, preemptee *v1.Pod) bool {
func IsCriticalPodBasedOnPriority(priority int32) bool {
return priority >= scheduling.SystemCriticalPriority
}
// IsNodeCriticalPod checks if the given pod is a system-node-critical
func IsNodeCriticalPod(pod *v1.Pod) bool {
return IsCriticalPod(pod) && (pod.Spec.PriorityClassName == scheduling.SystemNodeCritical)
}

View File

@ -24,22 +24,16 @@ import (
// code will forward to net library functions, and unit tests will override the methods
// for testing purposes.
type NetworkInterfacer interface {
Addrs(intf *net.Interface) ([]net.Addr, error)
Interfaces() ([]net.Interface, error)
InterfaceAddrs() ([]net.Addr, error)
}
// RealNetwork implements the NetworkInterfacer interface for production code, just
// wrapping the underlying net library function calls.
type RealNetwork struct{}
// Addrs wraps net.Interface.Addrs(), it's a part of NetworkInterfacer interface.
func (RealNetwork) Addrs(intf *net.Interface) ([]net.Addr, error) {
return intf.Addrs()
}
// Interfaces wraps net.Interfaces(), it's a part of NetworkInterfacer interface.
func (RealNetwork) Interfaces() ([]net.Interface, error) {
return net.Interfaces()
// InterfaceAddrs wraps net.InterfaceAddrs(), it's a part of NetworkInterfacer interface.
func (RealNetwork) InterfaceAddrs() ([]net.Addr, error) {
return net.InterfaceAddrs()
}
var _ NetworkInterfacer = &RealNetwork{}

View File

@ -29,7 +29,7 @@ import (
"k8s.io/apimachinery/pkg/types"
utilrand "k8s.io/apimachinery/pkg/util/rand"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/tools/events"
helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
utilsysctl "k8s.io/kubernetes/pkg/util/sysctl"
utilnet "k8s.io/utils/net"
@ -162,7 +162,7 @@ func GetLocalAddrs() ([]net.IP, error) {
func GetLocalAddrSet() utilnet.IPSet {
localAddrs, err := GetLocalAddrs()
if err != nil {
klog.ErrorS(err, "Failed to get local addresses assuming no local IPs", err)
klog.ErrorS(err, "Failed to get local addresses assuming no local IPs")
} else if len(localAddrs) == 0 {
klog.InfoS("No local addresses were found")
}
@ -209,9 +209,9 @@ func GetNodeAddresses(cidrs []string, nw NetworkInterfacer) (sets.String, error)
}
}
itfs, err := nw.Interfaces()
addrs, err := nw.InterfaceAddrs()
if err != nil {
return nil, fmt.Errorf("error listing all interfaces from host, error: %v", err)
return nil, fmt.Errorf("error listing all interfaceAddrs from host, error: %v", err)
}
// Second round of iteration to parse IPs based on cidr.
@ -221,29 +221,24 @@ func GetNodeAddresses(cidrs []string, nw NetworkInterfacer) (sets.String, error)
}
_, ipNet, _ := net.ParseCIDR(cidr)
for _, itf := range itfs {
addrs, err := nw.Addrs(&itf)
if err != nil {
return nil, fmt.Errorf("error getting address from interface %s, error: %v", itf.Name, err)
for _, addr := range addrs {
var ip net.IP
// nw.InterfaceAddrs may return net.IPAddr or net.IPNet on windows, and it will return net.IPNet on linux.
switch v := addr.(type) {
case *net.IPAddr:
ip = v.IP
case *net.IPNet:
ip = v.IP
default:
continue
}
for _, addr := range addrs {
if addr == nil {
continue
if ipNet.Contains(ip) {
if utilnet.IsIPv6(ip) && !uniqueAddressList.Has(IPv6ZeroCIDR) {
uniqueAddressList.Insert(ip.String())
}
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
return nil, fmt.Errorf("error parsing CIDR for interface %s, error: %v", itf.Name, err)
}
if ipNet.Contains(ip) {
if utilnet.IsIPv6(ip) && !uniqueAddressList.Has(IPv6ZeroCIDR) {
uniqueAddressList.Insert(ip.String())
}
if !utilnet.IsIPv6(ip) && !uniqueAddressList.Has(IPv4ZeroCIDR) {
uniqueAddressList.Insert(ip.String())
}
if !utilnet.IsIPv6(ip) && !uniqueAddressList.Has(IPv4ZeroCIDR) {
uniqueAddressList.Insert(ip.String())
}
}
}
@ -257,7 +252,7 @@ func GetNodeAddresses(cidrs []string, nw NetworkInterfacer) (sets.String, error)
}
// LogAndEmitIncorrectIPVersionEvent logs and emits incorrect IP version event.
func LogAndEmitIncorrectIPVersionEvent(recorder record.EventRecorder, fieldName, fieldValue, svcNamespace, svcName string, svcUID types.UID) {
func LogAndEmitIncorrectIPVersionEvent(recorder events.EventRecorder, fieldName, fieldValue, svcNamespace, svcName string, svcUID types.UID) {
errMsg := fmt.Sprintf("%s in %s has incorrect IP version", fieldValue, fieldName)
klog.Errorf("%s (service %s/%s).", errMsg, svcNamespace, svcName)
if recorder != nil {
@ -267,7 +262,7 @@ func LogAndEmitIncorrectIPVersionEvent(recorder record.EventRecorder, fieldName,
Name: svcName,
Namespace: svcNamespace,
UID: svcUID,
}, v1.EventTypeWarning, "KubeProxyIncorrectIPVersion", errMsg)
}, nil, v1.EventTypeWarning, "KubeProxyIncorrectIPVersion", "GatherEndpoints", errMsg)
}
}
@ -477,6 +472,18 @@ func WriteLine(buf *bytes.Buffer, words ...string) {
}
}
// WriteRuleLine prepends the strings "-A" and chainName to the buffer and calls
// WriteLine to join all the words into the buffer and terminate with newline.
func WriteRuleLine(buf *bytes.Buffer, chainName string, words ...string) {
if len(words) == 0 {
return
}
buf.WriteString("-A ")
buf.WriteString(chainName)
buf.WriteByte(' ')
WriteLine(buf, words...)
}
// WriteBytesLine write bytes to buffer, terminate with newline
func WriteBytesLine(buf *bytes.Buffer, bytes []byte) {
buf.Write(bytes)

View File

@ -20,11 +20,11 @@ import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/opencontainers/runc/libcontainer/apparmor"
v1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
@ -107,7 +107,7 @@ func validateHost(runtime string) error {
}
// Check kernel support.
if !IsAppArmorEnabled() {
if !apparmor.IsEnabled() {
return errors.New("AppArmor is not enabled on the host")
}
@ -212,17 +212,3 @@ func getAppArmorFS() (string, error) {
return "", errors.New("securityfs not found")
}
// IsAppArmorEnabled returns true if apparmor is enabled for the host.
// This function is forked from
// https://github.com/opencontainers/runc/blob/1a81e9ab1f138c091fe5c86d0883f87716088527/libcontainer/apparmor/apparmor.go
// to avoid the libapparmor dependency.
func IsAppArmorEnabled() bool {
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
return err == nil && len(buf) > 1 && buf[0] == 'Y'
}
}
return false
}

87
vendor/k8s.io/kubernetes/pkg/volume/metrics_block.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
/*
Copyright 2021 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 volume
import (
"fmt"
"io"
"os"
"runtime"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ MetricsProvider = &metricsBlock{}
// metricsBlock represents a MetricsProvider that detects the size of the
// BlockMode Volume.
type metricsBlock struct {
// the device node where the volume is attached to.
device string
}
// NewMetricsStatfs creates a new metricsBlock with the device node of the
// Volume.
func NewMetricsBlock(device string) MetricsProvider {
return &metricsBlock{device}
}
// See MetricsProvider.GetMetrics
// GetMetrics detects the size of the BlockMode volume for the device node
// where the Volume is attached.
//
// Note that only the capacity of the device can be detected with standard
// tools. Storage systems may have more information that they can provide by
// going through specialized APIs.
func (mb *metricsBlock) GetMetrics() (*Metrics, error) {
// TODO: Windows does not yet support VolumeMode=Block
if runtime.GOOS == "windows" {
return nil, NewNotImplementedError("Windows does not support Block volumes")
}
metrics := &Metrics{Time: metav1.Now()}
if mb.device == "" {
return metrics, NewNoPathDefinedError()
}
err := mb.getBlockInfo(metrics)
if err != nil {
return metrics, err
}
return metrics, nil
}
// getBlockInfo fetches metrics.Capacity by opening the device and seeking to
// the end.
func (mb *metricsBlock) getBlockInfo(metrics *Metrics) error {
dev, err := os.Open(mb.device)
if err != nil {
return fmt.Errorf("unable to open device %q: %w", mb.device, err)
}
defer dev.Close()
end, err := dev.Seek(0, io.SeekEnd)
if err != nil {
return fmt.Errorf("failed to detect size of %q: %w", mb.device, err)
}
metrics.Capacity = resource.NewQuantity(end, resource.BinarySI)
return nil
}

View File

@ -46,12 +46,7 @@ func (md *metricsDu) GetMetrics() (*Metrics, error) {
return metrics, NewNoPathDefinedError()
}
err := md.runDiskUsage(metrics)
if err != nil {
return metrics, err
}
err = md.runFind(metrics)
err := md.getDiskUsage(metrics)
if err != nil {
return metrics, err
}
@ -64,23 +59,14 @@ func (md *metricsDu) GetMetrics() (*Metrics, error) {
return metrics, nil
}
// runDiskUsage gets disk usage of md.path and writes the results to metrics.Used
func (md *metricsDu) runDiskUsage(metrics *Metrics) error {
used, err := fs.DiskUsage(md.path)
// getDiskUsage writes metrics.Used and metric.InodesUsed from fs.DiskUsage
func (md *metricsDu) getDiskUsage(metrics *Metrics) error {
usage, err := fs.DiskUsage(md.path)
if err != nil {
return err
}
metrics.Used = used
return nil
}
// runFind executes the "find" command and writes the results to metrics.InodesUsed
func (md *metricsDu) runFind(metrics *Metrics) error {
inodesUsed, err := fs.Find(md.path)
if err != nil {
return err
}
metrics.InodesUsed = resource.NewQuantity(inodesUsed, resource.BinarySI)
metrics.Used = resource.NewQuantity(usage.Bytes, resource.BinarySI)
metrics.InodesUsed = resource.NewQuantity(usage.Inodes, resource.BinarySI)
return nil
}

View File

@ -35,6 +35,14 @@ func NewNotSupportedError() *MetricsError {
}
}
// NewNotImplementedError creates a new MetricsError with code NotSupported.
func NewNotImplementedError(reason string) *MetricsError {
return &MetricsError{
Code: ErrCodeNotSupported,
Msg: fmt.Sprintf("metrics support is not implemented: %s", reason),
}
}
// NewNotSupportedErrorWithDriverName creates a new MetricsError with code NotSupported.
// driver name is added to the error message.
func NewNotSupportedErrorWithDriverName(name string) *MetricsError {

View File

@ -23,6 +23,11 @@ var _ MetricsProvider = &MetricsNil{}
// metrics.
type MetricsNil struct{}
// SupportsMetrics returns false for the MetricsNil type.
func (*MetricsNil) SupportsMetrics() bool {
return false
}
// GetMetrics returns an empty Metrics and an error.
// See MetricsProvider.GetMetrics
func (*MetricsNil) GetMetrics() (*Metrics, error) {

View File

@ -71,8 +71,10 @@ const (
var (
deprecatedVolumeProviders = map[string]string{
"kubernetes.io/cinder": "The Cinder volume provider is deprecated and will be removed in a future release",
"kubernetes.io/scaleio": "The ScaleIO volume provider is deprecated and will be removed in a future release",
"kubernetes.io/cinder": "The Cinder volume provider is deprecated and will be removed in a future release",
"kubernetes.io/storageos": "The StorageOS volume provider is deprecated and will be removed in a future release",
"kubernetes.io/quobyte": "The Quobyte volume provider is deprecated and will be removed in a future release",
"kubernetes.io/flocker": "The Flocker volume provider is deprecated and will be removed in a future release",
}
)
@ -606,7 +608,7 @@ func (pm *VolumePluginMgr) InitPlugins(plugins []VolumePlugin, prober DynamicPlu
}
if err := pm.prober.Init(); err != nil {
// Prober init failure should not affect the initialization of other plugins.
klog.Errorf("Error initializing dynamic plugin prober: %s", err)
klog.ErrorS(err, "Error initializing dynamic plugin prober")
pm.prober = &dummyPluginProber{}
}
@ -631,12 +633,12 @@ func (pm *VolumePluginMgr) InitPlugins(plugins []VolumePlugin, prober DynamicPlu
}
err := plugin.Init(host)
if err != nil {
klog.Errorf("Failed to load volume plugin %s, error: %s", name, err.Error())
klog.ErrorS(err, "Failed to load volume plugin", "pluginName", name)
allErrs = append(allErrs, err)
continue
}
pm.plugins[name] = plugin
klog.V(1).Infof("Loaded volume plugin %q", name)
klog.V(1).InfoS("Loaded volume plugin", "pluginName", name)
}
return utilerrors.NewAggregate(allErrs)
}
@ -649,10 +651,10 @@ func (pm *VolumePluginMgr) initProbedPlugin(probedPlugin VolumePlugin) error {
err := probedPlugin.Init(pm.Host)
if err != nil {
return fmt.Errorf("Failed to load volume plugin %s, error: %s", name, err.Error())
return fmt.Errorf("failed to load volume plugin %s, error: %s", name, err.Error())
}
klog.V(1).Infof("Loaded volume plugin %q", name)
klog.V(1).InfoS("Loaded volume plugin", "pluginName", name)
return nil
}
@ -664,7 +666,7 @@ func (pm *VolumePluginMgr) FindPluginBySpec(spec *Spec) (VolumePlugin, error) {
defer pm.mutex.RUnlock()
if spec == nil {
return nil, fmt.Errorf("Could not find plugin because volume spec is nil")
return nil, fmt.Errorf("could not find plugin because volume spec is nil")
}
matches := []VolumePlugin{}
@ -745,15 +747,15 @@ func (pm *VolumePluginMgr) logDeprecation(plugin string) {
func (pm *VolumePluginMgr) refreshProbedPlugins() {
events, err := pm.prober.Probe()
if err != nil {
klog.Errorf("Error dynamically probing plugins: %s", err)
klog.ErrorS(err, "Error dynamically probing plugins")
return // Use cached plugins upon failure.
}
for _, event := range events {
if event.Op == ProbeAddOrUpdate {
if err := pm.initProbedPlugin(event.Plugin); err != nil {
klog.Errorf("Error initializing dynamically probed plugin %s; error: %s",
event.Plugin.GetPluginName(), err)
klog.ErrorS(err, "Error initializing dynamically probed plugin",
"pluginName", event.Plugin.GetPluginName())
continue
}
pm.probedPlugins[event.Plugin.GetPluginName()] = event.Plugin
@ -761,8 +763,8 @@ func (pm *VolumePluginMgr) refreshProbedPlugins() {
// Plugin is not available on ProbeRemove event, only PluginName
delete(pm.probedPlugins, event.PluginName)
} else {
klog.Errorf("Unknown Operation on PluginName: %s.",
event.Plugin.GetPluginName())
klog.ErrorS(nil, "Unknown Operation on PluginName.",
"pluginName", event.Plugin.GetPluginName())
}
}
}
@ -787,7 +789,7 @@ func (pm *VolumePluginMgr) ListVolumePluginWithLimits() []VolumePluginWithAttach
func (pm *VolumePluginMgr) FindPersistentPluginBySpec(spec *Spec) (PersistentVolumePlugin, error) {
volumePlugin, err := pm.FindPluginBySpec(spec)
if err != nil {
return nil, fmt.Errorf("Could not find volume plugin for spec: %#v", spec)
return nil, fmt.Errorf("could not find volume plugin for spec: %#v", spec)
}
if persistentVolumePlugin, ok := volumePlugin.(PersistentVolumePlugin); ok {
return persistentVolumePlugin, nil
@ -800,7 +802,7 @@ func (pm *VolumePluginMgr) FindPersistentPluginBySpec(spec *Spec) (PersistentVol
func (pm *VolumePluginMgr) FindVolumePluginWithLimitsBySpec(spec *Spec) (VolumePluginWithAttachLimits, error) {
volumePlugin, err := pm.FindPluginBySpec(spec)
if err != nil {
return nil, fmt.Errorf("Could not find volume plugin for spec : %#v", spec)
return nil, fmt.Errorf("could not find volume plugin for spec : %#v", spec)
}
if limitedPlugin, ok := volumePlugin.(VolumePluginWithAttachLimits); ok {
@ -956,10 +958,10 @@ func (pm *VolumePluginMgr) FindExpandablePluginBySpec(spec *Spec) (ExpandableVol
if spec.IsKubeletExpandable() {
// for kubelet expandable volumes, return a noop plugin that
// returns success for expand on the controller
klog.V(4).Infof("FindExpandablePluginBySpec(%s) -> returning noopExpandableVolumePluginInstance", spec.Name())
klog.V(4).InfoS("FindExpandablePluginBySpec -> returning noopExpandableVolumePluginInstance", "specName", spec.Name())
return &noopExpandableVolumePluginInstance{spec}, nil
}
klog.V(4).Infof("FindExpandablePluginBySpec(%s) -> err:%v", spec.Name(), err)
klog.V(4).InfoS("FindExpandablePluginBySpec -> err", "specName", spec.Name(), "err", err)
return nil, err
}

View File

@ -19,17 +19,21 @@ limitations under the License.
package fs
import (
"bytes"
"fmt"
"os/exec"
"strings"
"os"
"path/filepath"
"syscall"
"golang.org/x/sys/unix"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/kubernetes/pkg/volume/util/fsquota"
)
type UsageInfo struct {
Bytes int64
Inodes int64
}
// Info linux returns (available bytes, byte capacity, byte usage, total inodes, inodes free, inode usage, error)
// for the filesystem that path resides upon.
func Info(path string) (int64, int64, int64, int64, int64, int64, error) {
@ -55,63 +59,83 @@ func Info(path string) (int64, int64, int64, int64, int64, int64, error) {
return available, capacity, usage, inodes, inodesFree, inodesUsed, nil
}
// DiskUsage gets disk usage of specified path.
func DiskUsage(path string) (*resource.Quantity, error) {
// First check whether the quota system knows about this directory
// A nil quantity with no error means that the path does not support quotas
// and we should use other mechanisms.
data, err := fsquota.GetConsumption(path)
if data != nil {
return data, nil
} else if err != nil {
return nil, fmt.Errorf("unable to retrieve disk consumption via quota for %s: %v", path, err)
}
// Uses the same niceness level as cadvisor.fs does when running du
// Uses -B 1 to always scale to a blocksize of 1 byte
out, err := exec.Command("nice", "-n", "19", "du", "-x", "-s", "-B", "1", path).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("failed command 'du' ($ nice -n 19 du -x -s -B 1) on path %s with error %v", path, err)
}
used, err := resource.ParseQuantity(strings.Fields(string(out))[0])
if err != nil {
return nil, fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err)
}
used.Format = resource.BinarySI
return &used, nil
}
// DiskUsage calculates the number of inodes and disk usage for a given directory
func DiskUsage(path string) (UsageInfo, error) {
var usage UsageInfo
// Find uses the equivalent of the command `find <path> -dev -printf '.' | wc -c` to count files and directories.
// While this is not an exact measure of inodes used, it is a very good approximation.
func Find(path string) (int64, error) {
if path == "" {
return 0, fmt.Errorf("invalid directory")
return usage, fmt.Errorf("invalid directory")
}
// First check whether the quota system knows about this directory
// A nil quantity with no error means that the path does not support quotas
// and we should use other mechanisms.
inodes, err := fsquota.GetInodes(path)
// A nil quantity or error means that the path does not support quotas
// or xfs_quota tool is missing and we should use other mechanisms.
consumption, _ := fsquota.GetConsumption(path)
if consumption != nil {
usage.Bytes = consumption.Value()
}
inodes, _ := fsquota.GetInodes(path)
if inodes != nil {
return inodes.Value(), nil
} else if err != nil {
return 0, fmt.Errorf("unable to retrieve inode consumption via quota for %s: %v", path, err)
usage.Inodes = inodes.Value()
}
var counter byteCounter
var stderr bytes.Buffer
findCmd := exec.Command("find", path, "-xdev", "-printf", ".")
findCmd.Stdout, findCmd.Stderr = &counter, &stderr
if err := findCmd.Start(); err != nil {
return 0, fmt.Errorf("failed to exec cmd %v - %v; stderr: %v", findCmd.Args, err, stderr.String())
}
if err := findCmd.Wait(); err != nil {
return 0, fmt.Errorf("cmd %v failed. stderr: %s; err: %v", findCmd.Args, stderr.String(), err)
}
return counter.bytesWritten, nil
}
// Simple io.Writer implementation that counts how many bytes were written.
type byteCounter struct{ bytesWritten int64 }
if inodes != nil && consumption != nil {
return usage, nil
}
func (b *byteCounter) Write(p []byte) (int, error) {
b.bytesWritten += int64(len(p))
return len(p), nil
topLevelStat := &unix.Stat_t{}
err := unix.Stat(path, topLevelStat)
if err != nil {
return usage, err
}
// dedupedInode stores inodes that could be duplicates (nlink > 1)
dedupedInodes := make(map[uint64]struct{})
err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
// ignore files that have been deleted after directory was read
if os.IsNotExist(err) {
return nil
}
if err != nil {
return fmt.Errorf("unable to count inodes for %s: %s", path, err)
}
// according to the docs, Sys can be nil
if info.Sys() == nil {
return fmt.Errorf("fileinfo Sys is nil")
}
s, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("unsupported fileinfo; could not convert to stat_t")
}
if s.Dev != topLevelStat.Dev {
// don't descend into directories on other devices
return filepath.SkipDir
}
// Dedupe hardlinks
if s.Nlink > 1 {
if _, ok := dedupedInodes[s.Ino]; !ok {
dedupedInodes[s.Ino] = struct{}{}
} else {
return nil
}
}
if consumption == nil {
usage.Bytes += int64(s.Blocks) * int64(512) // blocksize in bytes
}
if inodes == nil {
usage.Inodes++
}
return nil
})
return usage, err
}

View File

@ -20,21 +20,20 @@ package fs
import (
"fmt"
"k8s.io/apimachinery/pkg/api/resource"
)
type UsageInfo struct {
Bytes int64
Inodes int64
}
// Info unsupported returns 0 values for available and capacity and an error.
func Info(path string) (int64, int64, int64, int64, int64, int64, error) {
return 0, 0, 0, 0, 0, 0, fmt.Errorf("fsinfo not supported for this build")
}
// DiskUsage gets disk usage of specified path.
func DiskUsage(path string) (*resource.Quantity, error) {
return nil, fmt.Errorf("du not supported for this build")
}
// Find will always return zero since is on unsupported platform.
func Find(path string) (int64, error) {
return 0, fmt.Errorf("find not supported for this build")
func DiskUsage(path string) (UsageInfo, error) {
var usage UsageInfo
return usage, fmt.Errorf("directory disk usage not supported for this build.")
}

View File

@ -26,8 +26,6 @@ import (
"unsafe"
"golang.org/x/sys/windows"
"k8s.io/apimachinery/pkg/api/resource"
)
var (
@ -35,6 +33,11 @@ var (
procGetDiskFreeSpaceEx = modkernel32.NewProc("GetDiskFreeSpaceExW")
)
type UsageInfo struct {
Bytes int64
Inodes int64
}
// Info returns (available bytes, byte capacity, byte usage, total inodes, inodes free, inode usage, error)
// for the filesystem that path resides upon.
func Info(path string) (int64, int64, int64, int64, int64, int64, error) {
@ -64,28 +67,15 @@ func Info(path string) (int64, int64, int64, int64, int64, int64, error) {
}
// DiskUsage gets disk usage of specified path.
func DiskUsage(path string) (*resource.Quantity, error) {
func DiskUsage(path string) (UsageInfo, error) {
var usage UsageInfo
info, err := os.Lstat(path)
if err != nil {
return nil, err
return usage, err
}
usage, err := diskUsage(path, info)
if err != nil {
return nil, err
}
used, err := resource.ParseQuantity(fmt.Sprintf("%d", usage))
if err != nil {
return nil, fmt.Errorf("failed to parse fs usage %d due to %v", usage, err)
}
used.Format = resource.BinarySI
return &used, nil
}
// Find will always return zero since inodes is not supported on Windows.
func Find(path string) (int64, error) {
return 0, nil
usage.Bytes, err = diskUsage(path, info)
return usage, err
}
func diskUsage(currPath string, info os.FileInfo) (int64, error) {

View File

@ -148,10 +148,7 @@ func IsOperationFinishedError(err error) bool {
// on PVC and actual filesystem on disk did not match
func IsFilesystemMismatchError(err error) bool {
mountError := mount.MountError{}
if errors.As(err, &mountError) && mountError.Type == mount.FilesystemMismatch {
return true
}
return false
return errors.As(err, &mountError) && mountError.Type == mount.FilesystemMismatch
}
// IsUncertainProgressError checks if given error is of type that indicates

View File

@ -48,6 +48,14 @@ type BlockVolume interface {
// and name of a symbolic link associated to a block device.
// ex. pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/, {volumeName}
GetPodDeviceMapPath() (string, string)
// SupportsMetrics should return true if the MetricsProvider is
// initialized
SupportsMetrics() bool
// MetricsProvider embeds methods for exposing metrics (e.g.
// used, available space).
MetricsProvider
}
// MetricsProvider exposes metrics (e.g. used,available space) related to a
@ -263,6 +271,11 @@ type Attacher interface {
WaitForAttach(spec *Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error)
}
// DeviceMounterArgs provides auxiliary, optional arguments to DeviceMounter.
type DeviceMounterArgs struct {
FsGroup *int64
}
// DeviceMounter can mount a block volume to a global path.
type DeviceMounter interface {
// GetDeviceMountPath returns a path where the device should
@ -277,7 +290,7 @@ type DeviceMounter interface {
// - TransientOperationFailure
// - UncertainProgressError
// - Error of any other type should be considered a final error
MountDevice(spec *Spec, devicePath string, deviceMountPath string) error
MountDevice(spec *Spec, devicePath string, deviceMountPath string, deviceMounterArgs DeviceMounterArgs) error
}
type BulkVolumeVerifier interface {

View File

@ -66,7 +66,7 @@ func SetVolumeOwnership(mounter Mounter, fsGroup *int64, fsGroupChangePolicy *v1
}
if skipPermissionChange(mounter, fsGroup, fsGroupChangePolicy) {
klog.V(3).Infof("skipping permission and ownership change for volume %s", mounter.GetPath())
klog.V(3).InfoS("Skipping permission and ownership change for volume", "path", mounter.GetPath())
return nil
}
@ -96,7 +96,7 @@ func legacyOwnershipChange(mounter Mounter, fsGroup *int64) error {
func changeFilePermission(filename string, fsGroup *int64, readonly bool, info os.FileInfo) error {
err := os.Lchown(filename, -1, int(*fsGroup))
if err != nil {
klog.Errorf("Lchown failed on %v: %v", filename, err)
klog.ErrorS(err, "Lchown failed", "path", filename)
}
// chmod passes through to the underlying file for symlinks.
@ -122,7 +122,7 @@ func changeFilePermission(filename string, fsGroup *int64, readonly bool, info o
err = os.Chmod(filename, info.Mode()|mask)
if err != nil {
klog.Errorf("Chmod failed on %v: %v", filename, err)
klog.ErrorS(err, "Chown failed", "path", filename)
}
return nil
@ -132,7 +132,7 @@ func skipPermissionChange(mounter Mounter, fsGroup *int64, fsGroupChangePolicy *
dir := mounter.GetPath()
if fsGroupChangePolicy == nil || *fsGroupChangePolicy != v1.FSGroupChangeOnRootMismatch {
klog.V(4).Infof("perform recursive ownership change for %s", dir)
klog.V(4).InfoS("Perform recursive ownership change for directory", "path", dir)
return false
}
return !requiresPermissionChange(mounter.GetPath(), fsGroup, mounter.GetAttributes().ReadOnly)
@ -141,17 +141,17 @@ func skipPermissionChange(mounter Mounter, fsGroup *int64, fsGroupChangePolicy *
func requiresPermissionChange(rootDir string, fsGroup *int64, readonly bool) bool {
fsInfo, err := os.Stat(rootDir)
if err != nil {
klog.Errorf("performing recursive ownership change on %s because reading permissions of root volume failed: %v", rootDir, err)
klog.ErrorS(err, "Performing recursive ownership change on rootDir because reading permissions of root volume failed", "path", rootDir)
return true
}
stat, ok := fsInfo.Sys().(*syscall.Stat_t)
if !ok || stat == nil {
klog.Errorf("performing recursive ownership change on %s because reading permissions of root volume failed", rootDir)
klog.ErrorS(nil, "Performing recursive ownership change on rootDir because reading permissions of root volume failed", "path", rootDir)
return true
}
if int(stat.Gid) != int(*fsGroup) {
klog.V(4).Infof("expected group ownership of volume %s did not match with: %d", rootDir, stat.Gid)
klog.V(4).InfoS("Expected group ownership of volume did not match with Gid", "path", rootDir, "GID", stat.Gid)
return true
}
unixPerms := rwMask
@ -175,7 +175,7 @@ func requiresPermissionChange(rootDir string, fsGroup *int64, readonly bool) boo
// unixPerms: 770, filePerms: 750 : 770&750 = 750 (perms on directory is NOT a superset)
// We also need to check if setgid bits are set in permissions of the directory.
if (unixPerms&filePerm != unixPerms) || (fsInfo.Mode()&os.ModeSetgid == 0) {
klog.V(4).Infof("performing recursive ownership change on %s because of mismatching mode", rootDir)
klog.V(4).InfoS("Performing recursive ownership change on rootDir because of mismatching mode", "path", rootDir)
return true
}
return false

View File

@ -18,10 +18,10 @@ package auth
import (
"context"
"fmt"
"sync"
"time"
"github.com/pkg/errors"
authorizationv1 "k8s.io/api/authorization/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -99,7 +99,7 @@ func BindClusterRole(c bindingsGetter, clusterRole, ns string, subjects ...rbacv
}, metav1.CreateOptions{})
if err != nil {
return errors.Wrapf(err, "binding clusterrole/%s for %q for %v", clusterRole, ns, subjects)
return fmt.Errorf("binding clusterrole/%s for %q for %v: %w", clusterRole, ns, subjects, err)
}
return nil
@ -136,7 +136,7 @@ func bindInNamespace(c bindingsGetter, roleType, role, ns string, subjects ...rb
}, metav1.CreateOptions{})
if err != nil {
return errors.Wrapf(err, "binding %s/%s into %q for %v", roleType, role, ns, subjects)
return fmt.Errorf("binding %s/%s into %q for %v: %w", roleType, role, ns, subjects, err)
}
return nil

View File

@ -17,6 +17,9 @@ limitations under the License.
package framework
import (
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
"reflect"
"runtime"
"sync"
)
@ -70,6 +73,7 @@ func RunCleanupActions() {
}()
// Run unlocked.
for _, fn := range list {
e2elog.Logf("Running Cleanup Action: %v", runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
fn()
}
}

View File

@ -296,7 +296,7 @@ func (f *Framework) BeforeEach() {
gatherMetricsAfterTest := TestContext.GatherMetricsAfterTest == "true" || TestContext.GatherMetricsAfterTest == "master"
if gatherMetricsAfterTest && TestContext.IncludeClusterAutoscalerMetrics {
grabber, err := e2emetrics.NewMetricsGrabber(f.ClientSet, f.KubemarkExternalClusterClientSet, !ProviderIs("kubemark"), false, false, false, TestContext.IncludeClusterAutoscalerMetrics)
grabber, err := e2emetrics.NewMetricsGrabber(f.ClientSet, f.KubemarkExternalClusterClientSet, f.ClientConfig(), !ProviderIs("kubemark"), false, false, false, TestContext.IncludeClusterAutoscalerMetrics, false)
if err != nil {
Logf("Failed to create MetricsGrabber (skipping ClusterAutoscaler metrics gathering before test): %v", err)
} else {
@ -449,7 +449,7 @@ func (f *Framework) AfterEach() {
ginkgo.By("Gathering metrics")
// Grab apiserver, scheduler, controller-manager metrics and (optionally) nodes' kubelet metrics.
grabMetricsFromKubelets := TestContext.GatherMetricsAfterTest != "master" && !ProviderIs("kubemark")
grabber, err := e2emetrics.NewMetricsGrabber(f.ClientSet, f.KubemarkExternalClusterClientSet, grabMetricsFromKubelets, true, true, true, TestContext.IncludeClusterAutoscalerMetrics)
grabber, err := e2emetrics.NewMetricsGrabber(f.ClientSet, f.KubemarkExternalClusterClientSet, f.ClientConfig(), grabMetricsFromKubelets, true, true, true, TestContext.IncludeClusterAutoscalerMetrics, false)
if err != nil {
Logf("Failed to create MetricsGrabber (skipping metrics gathering): %v", err)
} else {

View File

@ -142,6 +142,10 @@ func (tk *TestKubeconfig) WriteFileViaContainer(podName, containerName string, p
}
}
command := fmt.Sprintf("echo '%s' > '%s'; sync", contents, path)
// TODO(mauriciopoppe): remove this statement once we add `sync` to the test image, ref #101172
if e2epod.NodeOSDistroIs("windows") {
command = fmt.Sprintf("echo '%s' > '%s';", contents, path)
}
stdout, stderr, err := tk.kubectlExecWithRetry(tk.Namespace, podName, containerName, "--", "/bin/sh", "-c", command)
if err != nil {
e2elog.Logf("error running kubectl exec to write file: %v\nstdout=%v\nstderr=%v)", err, string(stdout), string(stderr))

View File

@ -139,7 +139,7 @@ func getKubeletMetricsFromNode(c clientset.Interface, nodeName string) (KubeletM
if c == nil {
return GrabKubeletMetricsWithoutProxy(nodeName, "/metrics")
}
grabber, err := NewMetricsGrabber(c, nil, true, false, false, false, false)
grabber, err := NewMetricsGrabber(c, nil, nil, true, false, false, false, false, false)
if err != nil {
return KubeletMetrics{}, err
}

View File

@ -18,62 +18,89 @@ package metrics
import (
"context"
"errors"
"fmt"
"net"
"regexp"
"sync"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
)
const (
// insecureSchedulerPort is the default port for the scheduler status server.
// May be overridden by a flag at startup.
// Deprecated: use the secure KubeSchedulerPort instead.
insecureSchedulerPort = 10251
// insecureKubeControllerManagerPort is the default port for the controller manager status server.
// May be overridden by a flag at startup.
// Deprecated: use the secure KubeControllerManagerPort instead.
insecureKubeControllerManagerPort = 10252
// kubeSchedulerPort is the default port for the scheduler status server.
kubeSchedulerPort = 10259
// kubeControllerManagerPort is the default port for the controller manager status server.
kubeControllerManagerPort = 10257
// snapshotControllerPort is the port for the snapshot controller
snapshotControllerPort = 9102
)
// MetricsGrabbingDisabledError is an error that is wrapped by the
// different MetricsGrabber.Wrap functions when metrics grabbing is
// not supported. Tests that check metrics data should then skip
// the check.
var MetricsGrabbingDisabledError = errors.New("metrics grabbing disabled")
// Collection is metrics collection of components
type Collection struct {
APIServerMetrics APIServerMetrics
ControllerManagerMetrics ControllerManagerMetrics
KubeletMetrics map[string]KubeletMetrics
SchedulerMetrics SchedulerMetrics
ClusterAutoscalerMetrics ClusterAutoscalerMetrics
APIServerMetrics APIServerMetrics
ControllerManagerMetrics ControllerManagerMetrics
SnapshotControllerMetrics SnapshotControllerMetrics
KubeletMetrics map[string]KubeletMetrics
SchedulerMetrics SchedulerMetrics
ClusterAutoscalerMetrics ClusterAutoscalerMetrics
}
// Grabber provides functions which grab metrics from components
type Grabber struct {
client clientset.Interface
externalClient clientset.Interface
grabFromAPIServer bool
grabFromControllerManager bool
grabFromKubelets bool
grabFromScheduler bool
grabFromClusterAutoscaler bool
kubeScheduler string
kubeControllerManager string
waitForControllerManagerReadyOnce sync.Once
client clientset.Interface
externalClient clientset.Interface
config *rest.Config
grabFromAPIServer bool
grabFromControllerManager bool
grabFromKubelets bool
grabFromScheduler bool
grabFromClusterAutoscaler bool
grabFromSnapshotController bool
kubeScheduler string
waitForSchedulerReadyOnce sync.Once
kubeControllerManager string
waitForControllerManagerReadyOnce sync.Once
snapshotController string
waitForSnapshotControllerReadyOnce sync.Once
}
// NewMetricsGrabber returns new metrics which are initialized.
func NewMetricsGrabber(c clientset.Interface, ec clientset.Interface, kubelets bool, scheduler bool, controllers bool, apiServer bool, clusterAutoscaler bool) (*Grabber, error) {
// NewMetricsGrabber prepares for grabbing metrics data from several different
// components. It should be called when those components are running because
// it needs to communicate with them to determine for which components
// metrics data can be retrieved.
//
// Collecting metrics data is an optional debug feature. Not all clusters will
// support it. If disabled for a component, the corresponding Grab function
// will immediately return an error derived from MetricsGrabbingDisabledError.
func NewMetricsGrabber(c clientset.Interface, ec clientset.Interface, config *rest.Config, kubelets bool, scheduler bool, controllers bool, apiServer bool, clusterAutoscaler bool, snapshotController bool) (*Grabber, error) {
kubeScheduler := ""
kubeControllerManager := ""
snapshotControllerManager := ""
regKubeScheduler := regexp.MustCompile("kube-scheduler-.*")
regKubeControllerManager := regexp.MustCompile("kube-controller-manager-.*")
regSnapshotController := regexp.MustCompile("volume-snapshot-controller.*")
if (scheduler || controllers) && config == nil {
return nil, errors.New("a rest config is required for grabbing kube-controller and kube-controller-manager metrics")
}
podList, err := c.CoreV1().Pods(metav1.NamespaceSystem).List(context.TODO(), metav1.ListOptions{})
if err != nil {
@ -89,35 +116,55 @@ func NewMetricsGrabber(c clientset.Interface, ec clientset.Interface, kubelets b
if regKubeControllerManager.MatchString(pod.Name) {
kubeControllerManager = pod.Name
}
if kubeScheduler != "" && kubeControllerManager != "" {
if regSnapshotController.MatchString(pod.Name) {
snapshotControllerManager = pod.Name
}
if kubeScheduler != "" && kubeControllerManager != "" && snapshotControllerManager != "" {
break
}
}
if kubeScheduler == "" {
scheduler = false
klog.Warningf("Can't find kube-scheduler pod. Grabbing metrics from kube-scheduler is disabled.")
}
if kubeControllerManager == "" {
controllers = false
klog.Warningf("Can't find kube-controller-manager pod. Grabbing metrics from kube-controller-manager is disabled.")
}
if ec == nil {
if clusterAutoscaler && ec == nil {
klog.Warningf("Did not receive an external client interface. Grabbing metrics from ClusterAutoscaler is disabled.")
}
return &Grabber{
client: c,
externalClient: ec,
grabFromAPIServer: apiServer,
grabFromControllerManager: controllers,
grabFromKubelets: kubelets,
grabFromScheduler: scheduler,
grabFromClusterAutoscaler: clusterAutoscaler,
kubeScheduler: kubeScheduler,
kubeControllerManager: kubeControllerManager,
client: c,
externalClient: ec,
config: config,
grabFromAPIServer: apiServer,
grabFromControllerManager: checkPodDebugHandlers(c, controllers, "kube-controller-manager", kubeControllerManager),
grabFromKubelets: kubelets,
grabFromScheduler: checkPodDebugHandlers(c, scheduler, "kube-scheduler", kubeScheduler),
grabFromClusterAutoscaler: clusterAutoscaler,
grabFromSnapshotController: checkPodDebugHandlers(c, snapshotController, "snapshot-controller", snapshotControllerManager),
kubeScheduler: kubeScheduler,
kubeControllerManager: kubeControllerManager,
snapshotController: snapshotControllerManager,
}, nil
}
func checkPodDebugHandlers(c clientset.Interface, requested bool, component, podName string) bool {
if !requested {
return false
}
if podName == "" {
klog.Warningf("Can't find %s pod. Grabbing metrics from %s is disabled.", component, component)
return false
}
// The debug handlers on the host where the pod runs might be disabled.
// We can check that indirectly by trying to retrieve log output.
limit := int64(1)
if _, err := c.CoreV1().Pods(metav1.NamespaceSystem).GetLogs(podName, &v1.PodLogOptions{LimitBytes: &limit}).DoRaw(context.TODO()); err != nil {
klog.Warningf("Can't retrieve log output of %s (%q). Debug handlers might be disabled in kubelet. Grabbing metrics from %s is disabled.",
podName, err, component)
return false
}
// Metrics gathering enabled.
return true
}
// HasControlPlanePods returns true if metrics grabber was able to find control-plane pods
func (g *Grabber) HasControlPlanePods() bool {
return g.kubeScheduler != "" && g.kubeControllerManager != ""
@ -149,20 +196,38 @@ func (g *Grabber) grabFromKubeletInternal(nodeName string, kubeletPort int) (Kub
// GrabFromScheduler returns metrics from scheduler
func (g *Grabber) GrabFromScheduler() (SchedulerMetrics, error) {
if g.kubeScheduler == "" {
return SchedulerMetrics{}, fmt.Errorf("kube-scheduler pod is not registered. Skipping Scheduler's metrics gathering")
if !g.grabFromScheduler {
return SchedulerMetrics{}, fmt.Errorf("kube-scheduler: %w", MetricsGrabbingDisabledError)
}
output, err := g.getMetricsFromPod(g.client, g.kubeScheduler, metav1.NamespaceSystem, insecureSchedulerPort)
var err error
g.waitForSchedulerReadyOnce.Do(func() {
if readyErr := e2epod.WaitForPodsReady(g.client, metav1.NamespaceSystem, g.kubeScheduler, 0); readyErr != nil {
err = fmt.Errorf("error waiting for kube-scheduler pod to be ready: %w", readyErr)
}
})
if err != nil {
return SchedulerMetrics{}, err
}
var lastMetricsFetchErr error
var output string
if metricsWaitErr := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
output, lastMetricsFetchErr = g.getSecureMetricsFromPod(g.kubeScheduler, metav1.NamespaceSystem, kubeSchedulerPort)
return lastMetricsFetchErr == nil, nil
}); metricsWaitErr != nil {
err := fmt.Errorf("error waiting for kube-scheduler pod to expose metrics: %v; %v", metricsWaitErr, lastMetricsFetchErr)
return SchedulerMetrics{}, err
}
return parseSchedulerMetrics(output)
}
// GrabFromClusterAutoscaler returns metrics from cluster autoscaler
func (g *Grabber) GrabFromClusterAutoscaler() (ClusterAutoscalerMetrics, error) {
if !g.HasControlPlanePods() && g.externalClient == nil {
return ClusterAutoscalerMetrics{}, fmt.Errorf("Did not find control-plane pods. Skipping ClusterAutoscaler's metrics gathering")
return ClusterAutoscalerMetrics{}, fmt.Errorf("ClusterAutoscaler: %w", MetricsGrabbingDisabledError)
}
var client clientset.Interface
var namespace string
@ -182,38 +247,73 @@ func (g *Grabber) GrabFromClusterAutoscaler() (ClusterAutoscalerMetrics, error)
// GrabFromControllerManager returns metrics from controller manager
func (g *Grabber) GrabFromControllerManager() (ControllerManagerMetrics, error) {
if g.kubeControllerManager == "" {
return ControllerManagerMetrics{}, fmt.Errorf("kube-controller-manager pod is not registered. Skipping ControllerManager's metrics gathering")
if !g.grabFromControllerManager {
return ControllerManagerMetrics{}, fmt.Errorf("kube-controller-manager: %w", MetricsGrabbingDisabledError)
}
var err error
podName := g.kubeControllerManager
g.waitForControllerManagerReadyOnce.Do(func() {
if readyErr := e2epod.WaitForPodsReady(g.client, metav1.NamespaceSystem, podName, 0); readyErr != nil {
err = fmt.Errorf("error waiting for controller manager pod to be ready: %w", readyErr)
return
}
var lastMetricsFetchErr error
if metricsWaitErr := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
_, lastMetricsFetchErr = g.getMetricsFromPod(g.client, podName, metav1.NamespaceSystem, insecureKubeControllerManagerPort)
return lastMetricsFetchErr == nil, nil
}); metricsWaitErr != nil {
err = fmt.Errorf("error waiting for controller manager pod to expose metrics: %v; %v", metricsWaitErr, lastMetricsFetchErr)
return
g.waitForControllerManagerReadyOnce.Do(func() {
if readyErr := e2epod.WaitForPodsReady(g.client, metav1.NamespaceSystem, g.kubeControllerManager, 0); readyErr != nil {
err = fmt.Errorf("error waiting for kube-controller-manager pod to be ready: %w", readyErr)
}
})
if err != nil {
return ControllerManagerMetrics{}, err
}
output, err := g.getMetricsFromPod(g.client, podName, metav1.NamespaceSystem, insecureKubeControllerManagerPort)
if err != nil {
var output string
var lastMetricsFetchErr error
if metricsWaitErr := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
output, lastMetricsFetchErr = g.getSecureMetricsFromPod(g.kubeControllerManager, metav1.NamespaceSystem, kubeControllerManagerPort)
return lastMetricsFetchErr == nil, nil
}); metricsWaitErr != nil {
err := fmt.Errorf("error waiting for kube-controller-manager to expose metrics: %v; %v", metricsWaitErr, lastMetricsFetchErr)
return ControllerManagerMetrics{}, err
}
return parseControllerManagerMetrics(output)
}
// GrabFromSnapshotController returns metrics from controller manager
func (g *Grabber) GrabFromSnapshotController(podName string, port int) (SnapshotControllerMetrics, error) {
if !g.grabFromSnapshotController {
return SnapshotControllerMetrics{}, fmt.Errorf("volume-snapshot-controller: %w", MetricsGrabbingDisabledError)
}
// Use overrides if provided via test config flags.
// Otherwise, use the default volume-snapshot-controller pod name and port.
if podName == "" {
podName = g.snapshotController
}
if port == 0 {
port = snapshotControllerPort
}
var err error
g.waitForSnapshotControllerReadyOnce.Do(func() {
if readyErr := e2epod.WaitForPodsReady(g.client, metav1.NamespaceSystem, podName, 0); readyErr != nil {
err = fmt.Errorf("error waiting for volume-snapshot-controller pod to be ready: %w", readyErr)
}
})
if err != nil {
return SnapshotControllerMetrics{}, err
}
var output string
var lastMetricsFetchErr error
if metricsWaitErr := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
output, lastMetricsFetchErr = g.getMetricsFromPod(g.client, podName, metav1.NamespaceSystem, port)
return lastMetricsFetchErr == nil, nil
}); metricsWaitErr != nil {
err = fmt.Errorf("error waiting for volume-snapshot-controller pod to expose metrics: %v; %v", metricsWaitErr, lastMetricsFetchErr)
return SnapshotControllerMetrics{}, err
}
return parseSnapshotControllerMetrics(output)
}
// GrabFromAPIServer returns metrics from API server
func (g *Grabber) GrabFromAPIServer() (APIServerMetrics, error) {
output, err := g.getMetricsFromAPIServer()
@ -251,6 +351,14 @@ func (g *Grabber) Grab() (Collection, error) {
result.ControllerManagerMetrics = metrics
}
}
if g.grabFromSnapshotController {
metrics, err := g.GrabFromSnapshotController(g.snapshotController, snapshotControllerPort)
if err != nil {
errs = append(errs, err)
} else {
result.SnapshotControllerMetrics = metrics
}
}
if g.grabFromClusterAutoscaler {
metrics, err := g.GrabFromClusterAutoscaler()
if err != nil {
@ -281,12 +389,13 @@ func (g *Grabber) Grab() (Collection, error) {
return result, nil
}
// getMetricsFromPod retrieves metrics data from an insecure port.
func (g *Grabber) getMetricsFromPod(client clientset.Interface, podName string, namespace string, port int) (string, error) {
rawOutput, err := client.CoreV1().RESTClient().Get().
Namespace(namespace).
Resource("pods").
SubResource("proxy").
Name(fmt.Sprintf("%v:%v", podName, port)).
Name(fmt.Sprintf("%s:%d", podName, port)).
Suffix("metrics").
Do(context.TODO()).Raw()
if err != nil {
@ -294,3 +403,50 @@ func (g *Grabber) getMetricsFromPod(client clientset.Interface, podName string,
}
return string(rawOutput), nil
}
// getSecureMetricsFromPod retrieves metrics from a pod that uses TLS
// and checks client credentials. Conceptually this function is
// similar to "kubectl port-forward" + "kubectl get --raw
// https://localhost:<port>/metrics". It uses the same credentials
// as kubelet.
func (g *Grabber) getSecureMetricsFromPod(podName string, namespace string, port int) (string, error) {
dialer := e2epod.NewDialer(g.client, g.config)
metricConfig := rest.CopyConfig(g.config)
addr := e2epod.Addr{
Namespace: namespace,
PodName: podName,
Port: port,
}
metricConfig.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContainerPort(ctx, addr)
}
// This should make it possible verify the server, but while it
// got past the server name check, certificate validation
// still failed.
metricConfig.Host = addr.String()
metricConfig.ServerName = "localhost"
// Verifying the pod certificate with the same root CA
// as for the API server led to an error about "unknown root
// certificate". Disabling certificate checking on the client
// side gets around that and should be good enough for
// E2E testing.
metricConfig.Insecure = true
metricConfig.CAFile = ""
metricConfig.CAData = nil
// clientset.NewForConfig is used because
// metricClient.RESTClient() is directly usable, in contrast
// to the client constructed by rest.RESTClientFor().
metricClient, err := clientset.NewForConfig(metricConfig)
if err != nil {
return "", err
}
rawOutput, err := metricClient.RESTClient().Get().
AbsPath("metrics").
Do(context.TODO()).Raw()
if err != nil {
return "", err
}
return string(rawOutput), nil
}

View File

@ -0,0 +1,40 @@
/*
Copyright 2021 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 metrics
import "k8s.io/component-base/metrics/testutil"
// SnapshotControllerMetrics is metrics for controller manager
type SnapshotControllerMetrics testutil.Metrics
// Equal returns true if all metrics are the same as the arguments.
func (m *SnapshotControllerMetrics) Equal(o SnapshotControllerMetrics) bool {
return (*testutil.Metrics)(m).Equal(testutil.Metrics(o))
}
func newSnapshotControllerMetrics() SnapshotControllerMetrics {
result := testutil.NewMetrics()
return SnapshotControllerMetrics(result)
}
func parseSnapshotControllerMetrics(data string) (SnapshotControllerMetrics, error) {
result := newSnapshotControllerMetrics()
if err := testutil.ParseMetrics(data, (*testutil.Metrics)(&result)); err != nil {
return SnapshotControllerMetrics{}, err
}
return result, nil
}

View File

@ -561,6 +561,31 @@ func GetClusterZones(c clientset.Interface) (sets.String, error) {
return zones, nil
}
// GetSchedulableClusterZones returns the values of zone label collected from all nodes which are schedulable.
func GetSchedulableClusterZones(c clientset.Interface) (sets.String, error) {
nodes, err := c.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("Error getting nodes while attempting to list cluster zones: %v", err)
}
// collect values of zone label from all nodes
zones := sets.NewString()
for _, node := range nodes.Items {
// We should have at least 1 node in the zone which is schedulable.
if !IsNodeSchedulable(&node) {
continue
}
if zone, found := node.Labels[v1.LabelFailureDomainBetaZone]; found {
zones.Insert(zone)
}
if zone, found := node.Labels[v1.LabelTopologyZone]; found {
zones.Insert(zone)
}
}
return zones, nil
}
// CreatePodsPerNodeForSimpleApp creates pods w/ labels. Useful for tests which make a bunch of pods w/o any networking.
func CreatePodsPerNodeForSimpleApp(c clientset.Interface, namespace, appName string, podSpec func(n v1.Node) v1.PodSpec, maxCount int) map[string]string {
nodes, err := GetBoundedReadySchedulableNodes(c, maxCount)

View File

@ -214,8 +214,12 @@ func CheckReadyForTests(c clientset.Interface, nonblockingTaints string, allowed
}
allNodes, err := c.CoreV1().Nodes().List(context.TODO(), opts)
if err != nil {
var terminalListNodesErr error
e2elog.Logf("Unexpected error listing nodes: %v", err)
return false, err
if attempt >= 3 {
terminalListNodesErr = err
}
return false, terminalListNodesErr
}
for _, node := range allNodes.Items {
if !readyForTests(&node, nonblockingTaints) {

View File

@ -34,7 +34,7 @@ import (
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
)
const etcdImage = "3.4.13-0"
const etcdImage = "3.5.0-0"
// EtcdUpgrade upgrades etcd on GCE.
func EtcdUpgrade(targetStorage, targetVersion string) error {

View File

@ -19,7 +19,6 @@ package pod
import (
"context"
"fmt"
"runtime"
"time"
v1 "k8s.io/api/core/v1"
@ -154,15 +153,7 @@ func MakePod(ns string, nodeSelector map[string]string, pvclaims []*v1.Persisten
RestartPolicy: v1.RestartPolicyOnFailure,
},
}
var volumeMounts = make([]v1.VolumeMount, len(pvclaims))
var volumes = make([]v1.Volume, len(pvclaims))
for index, pvclaim := range pvclaims {
volumename := fmt.Sprintf("volume%v", index+1)
volumeMounts[index] = v1.VolumeMount{Name: volumename, MountPath: "/mnt/" + volumename}
volumes[index] = v1.Volume{Name: volumename, VolumeSource: v1.VolumeSource{PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: pvclaim.Name, ReadOnly: false}}}
}
podSpec.Spec.Containers[0].VolumeMounts = volumeMounts
podSpec.Spec.Volumes = volumes
setVolumes(&podSpec.Spec, pvclaims, nil /*inline volume sources*/, false /*PVCs readonly*/)
if nodeSelector != nil {
podSpec.Spec.NodeSelector = nodeSelector
}
@ -175,11 +166,12 @@ func MakeSecPod(podConfig *Config) (*v1.Pod, error) {
if podConfig.NS == "" {
return nil, fmt.Errorf("Cannot create pod with empty namespace")
}
if len(podConfig.Command) == 0 {
if len(podConfig.Command) == 0 && !NodeOSDistroIs("windows") {
podConfig.Command = "trap exit TERM; while true; do sleep 1; done"
}
podName := "pod-" + string(uuid.NewUUID())
if podConfig.FsGroup == nil && runtime.GOOS != "windows" {
if podConfig.FsGroup == nil && !NodeOSDistroIs("windows") {
podConfig.FsGroup = func(i int64) *int64 {
return &i
}(1000)
@ -223,33 +215,42 @@ func MakePodSpec(podConfig *Config) *v1.PodSpec {
podSpec.SecurityContext.FSGroupChangePolicy = podConfig.PodFSGroupChangePolicy
}
setVolumes(podSpec, podConfig.PVCs, podConfig.InlineVolumeSources, podConfig.PVCsReadOnly)
SetNodeSelection(podSpec, podConfig.NodeSelection)
return podSpec
}
func setVolumes(podSpec *v1.PodSpec, pvcs []*v1.PersistentVolumeClaim, inlineVolumeSources []*v1.VolumeSource, pvcsReadOnly bool) {
var volumeMounts = make([]v1.VolumeMount, 0)
var volumeDevices = make([]v1.VolumeDevice, 0)
var volumes = make([]v1.Volume, len(podConfig.PVCs)+len(podConfig.InlineVolumeSources))
var volumes = make([]v1.Volume, len(pvcs)+len(inlineVolumeSources))
volumeIndex := 0
for _, pvclaim := range podConfig.PVCs {
for _, pvclaim := range pvcs {
volumename := fmt.Sprintf("volume%v", volumeIndex+1)
if pvclaim.Spec.VolumeMode != nil && *pvclaim.Spec.VolumeMode == v1.PersistentVolumeBlock {
volumeDevices = append(volumeDevices, v1.VolumeDevice{Name: volumename, DevicePath: "/mnt/" + volumename})
} else {
volumeMounts = append(volumeMounts, v1.VolumeMount{Name: volumename, MountPath: "/mnt/" + volumename})
}
volumes[volumeIndex] = v1.Volume{Name: volumename, VolumeSource: v1.VolumeSource{PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: pvclaim.Name, ReadOnly: podConfig.PVCsReadOnly}}}
volumes[volumeIndex] = v1.Volume{
Name: volumename,
VolumeSource: v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: pvclaim.Name,
ReadOnly: pvcsReadOnly,
},
},
}
volumeIndex++
}
for _, src := range podConfig.InlineVolumeSources {
for _, src := range inlineVolumeSources {
volumename := fmt.Sprintf("volume%v", volumeIndex+1)
// In-line volumes can be only filesystem, not block.
volumeMounts = append(volumeMounts, v1.VolumeMount{Name: volumename, MountPath: "/mnt/" + volumename})
volumes[volumeIndex] = v1.Volume{Name: volumename, VolumeSource: *src}
volumeIndex++
}
podSpec.Containers[0].VolumeMounts = volumeMounts
podSpec.Containers[0].VolumeDevices = volumeDevices
podSpec.Volumes = volumes
SetNodeSelection(podSpec, podConfig.NodeSelection)
return podSpec
}

215
vendor/k8s.io/kubernetes/test/e2e/framework/pod/dial.go generated vendored Normal file
View File

@ -0,0 +1,215 @@
/*
Copyright 2021 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 pod
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"regexp"
"strconv"
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
"k8s.io/klog/v2"
)
// NewTransport creates a transport which uses the port forward dialer.
// URLs must use <namespace>.<pod>:<port> as host.
func NewTransport(client kubernetes.Interface, restConfig *rest.Config) *http.Transport {
return &http.Transport{
DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
dialer := NewDialer(client, restConfig)
a, err := ParseAddr(addr)
if err != nil {
return nil, err
}
return dialer.DialContainerPort(ctx, *a)
},
}
}
// NewDialer creates a dialer that supports connecting to container ports.
func NewDialer(client kubernetes.Interface, restConfig *rest.Config) *Dialer {
return &Dialer{
client: client,
restConfig: restConfig,
}
}
// Dialer holds the relevant parameters that are independent of a particular connection.
type Dialer struct {
client kubernetes.Interface
restConfig *rest.Config
}
// DialContainerPort connects to a certain container port in a pod.
func (d *Dialer) DialContainerPort(ctx context.Context, addr Addr) (conn net.Conn, finalErr error) {
restClient := d.client.CoreV1().RESTClient()
restConfig := d.restConfig
if restConfig.GroupVersion == nil {
restConfig.GroupVersion = &schema.GroupVersion{}
}
if restConfig.NegotiatedSerializer == nil {
restConfig.NegotiatedSerializer = scheme.Codecs
}
// The setup code around the actual portforward is from
// https://github.com/kubernetes/kubernetes/blob/c652ffbe4a29143623a1aaec39f745575f7e43ad/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go
req := restClient.Post().
Resource("pods").
Namespace(addr.Namespace).
Name(addr.PodName).
SubResource("portforward")
transport, upgrader, err := spdy.RoundTripperFor(restConfig)
if err != nil {
return nil, fmt.Errorf("create round tripper: %v", err)
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL())
streamConn, _, err := dialer.Dial(portforward.PortForwardProtocolV1Name)
if err != nil {
return nil, fmt.Errorf("dialer failed: %v", err)
}
requestID := "1"
defer func() {
if finalErr != nil {
streamConn.Close()
}
}()
// create error stream
headers := http.Header{}
headers.Set(v1.StreamType, v1.StreamTypeError)
headers.Set(v1.PortHeader, fmt.Sprintf("%d", addr.Port))
headers.Set(v1.PortForwardRequestIDHeader, requestID)
// We're not writing to this stream, just reading an error message from it.
// This happens asynchronously.
errorStream, err := streamConn.CreateStream(headers)
if err != nil {
return nil, fmt.Errorf("error creating error stream: %v", err)
}
errorStream.Close()
go func() {
message, err := ioutil.ReadAll(errorStream)
switch {
case err != nil:
klog.ErrorS(err, "error reading from error stream")
case len(message) > 0:
klog.ErrorS(errors.New(string(message)), "an error occurred connecting to the remote port")
}
}()
// create data stream
headers.Set(v1.StreamType, v1.StreamTypeData)
dataStream, err := streamConn.CreateStream(headers)
if err != nil {
return nil, fmt.Errorf("error creating data stream: %v", err)
}
return &stream{
Stream: dataStream,
streamConn: streamConn,
}, nil
}
// Addr contains all relevant parameters for a certain port in a pod.
// The container should be running before connections are attempted,
// otherwise the connection will fail.
type Addr struct {
Namespace, PodName string
Port int
}
var _ net.Addr = Addr{}
func (a Addr) Network() string {
return "port-forwarding"
}
func (a Addr) String() string {
return fmt.Sprintf("%s.%s:%d", a.Namespace, a.PodName, a.Port)
}
// ParseAddr expects a <namespace>.<pod>:<port number> as produced
// by Addr.String.
func ParseAddr(addr string) (*Addr, error) {
parts := addrRegex.FindStringSubmatch(addr)
if parts == nil {
return nil, fmt.Errorf("%q: must match the format <namespace>.<pod>:<port number>", addr)
}
port, _ := strconv.Atoi(parts[3])
return &Addr{
Namespace: parts[1],
PodName: parts[2],
Port: port,
}, nil
}
var addrRegex = regexp.MustCompile(`^([^\.]+)\.([^:]+):(\d+)$`)
type stream struct {
addr Addr
httpstream.Stream
streamConn httpstream.Connection
}
var _ net.Conn = &stream{}
func (s *stream) Close() error {
s.Stream.Close()
s.streamConn.Close()
return nil
}
func (s *stream) LocalAddr() net.Addr {
return LocalAddr{}
}
func (s *stream) RemoteAddr() net.Addr {
return s.addr
}
func (s *stream) SetDeadline(t time.Time) error {
return nil
}
func (s *stream) SetReadDeadline(t time.Time) error {
return nil
}
func (s *stream) SetWriteDeadline(t time.Time) error {
return nil
}
type LocalAddr struct{}
var _ net.Addr = LocalAddr{}
func (l LocalAddr) Network() string { return "port-forwarding" }
func (l LocalAddr) String() string { return "apiserver" }

View File

@ -25,7 +25,6 @@ import (
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
@ -34,6 +33,7 @@ import (
clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
"k8s.io/kubectl/pkg/util/podutils"
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
testutils "k8s.io/kubernetes/test/utils"
imageutils "k8s.io/kubernetes/test/utils/image"

View File

@ -37,14 +37,9 @@ func NodeOSDistroIs(distro string) bool {
}
// GenerateScriptCmd generates the corresponding command lines to execute a command.
// Depending on the Node OS is Windows or linux, the command will use powershell or /bin/sh
func GenerateScriptCmd(command string) []string {
var commands []string
if !NodeOSDistroIs("windows") {
commands = []string{"/bin/sh", "-c", command}
} else {
commands = []string{"powershell", "/c", command}
}
commands = []string{"/bin/sh", "-c", command}
return commands
}

View File

@ -435,6 +435,21 @@ func PodsResponding(c clientset.Interface, ns, name string, wantName bool, pods
return wait.PollImmediate(poll, podRespondingTimeout, NewProxyResponseChecker(c, ns, label, name, wantName, pods).CheckAllResponses)
}
// WaitForNumberOfPods waits up to timeout to ensure there are exact
// `num` pods in namespace `ns`.
// It returns the matching Pods or a timeout error.
func WaitForNumberOfPods(c clientset.Interface, ns string, num int, timeout time.Duration) (pods *v1.PodList, err error) {
err = wait.PollImmediate(poll, timeout, func() (bool, error) {
pods, err = c.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{})
// ignore intermittent network error
if err != nil {
return false, nil
}
return len(pods.Items) == num, nil
})
return
}
// WaitForPodsWithLabelScheduled waits for all matching pods to become scheduled and at least one
// matching pod exists. Return the list of matching pods.
func WaitForPodsWithLabelScheduled(c clientset.Interface, ns string, label labels.Selector) (pods *v1.PodList, err error) {

View File

@ -181,7 +181,7 @@ func (c *PodClient) mungeSpec(pod *v1.Pod) {
c := &pod.Spec.Containers[i]
if c.ImagePullPolicy == v1.PullAlways {
// If the image pull policy is PullAlways, the image doesn't need to be in
// the white list or pre-pulled, because the image is expected to be pulled
// the allow list or pre-pulled, because the image is expected to be pulled
// in the test anyway.
continue
}

View File

@ -22,10 +22,6 @@ const (
// KubeletPort is the default port for the kubelet server on each host machine.
// May be overridden by a flag at startup.
KubeletPort = 10250
// InsecureKubeControllerManagerPort is the default port for the controller manager status server.
// May be overridden by a flag at startup.
// Deprecated: use the secure KubeControllerManagerPort instead.
InsecureKubeControllerManagerPort = 10252
// KubeControllerManagerPort is the default port for the controller manager status server.
// May be overridden by a flag at startup.
KubeControllerManagerPort = 10257

View File

@ -21,8 +21,6 @@ import (
"os"
"sync"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
clientset "k8s.io/client-go/kubernetes"
)
@ -78,7 +76,7 @@ func SetupProviderConfig(providerName string) (ProviderInterface, error) {
defer mutex.Unlock()
factory, ok := providers[providerName]
if !ok {
return nil, errors.Wrapf(os.ErrNotExist, "The provider %s is unknown.", providerName)
return nil, fmt.Errorf("The provider %s is unknown: %w", providerName, os.ErrNotExist)
}
provider, err := factory()

View File

@ -177,7 +177,13 @@ func CreatePrivilegedPSPBinding(kubeClient clientset.Interface, namespace string
Kind: rbacv1.ServiceAccountKind,
Namespace: namespace,
Name: "default",
})
},
rbacv1.Subject{
Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName,
Name: "system:serviceaccounts:" + namespace,
},
)
ExpectNoError(err)
ExpectNoError(e2eauth.WaitForNamedAuthorizationUpdate(kubeClient.AuthorizationV1(),
serviceaccount.MakeUsername(namespace, "default"), namespace, "use", podSecurityPolicyPrivileged,

View File

@ -49,6 +49,7 @@ var localStorageCapacityIsolation featuregate.Feature = "LocalStorageCapacityIso
var (
downwardAPIHugePages featuregate.Feature = "DownwardAPIHugePages"
execProbeTimeout featuregate.Feature = "ExecProbeTimeout"
csiMigration featuregate.Feature = "CSIMigration"
)
func skipInternalf(caller int, format string, args ...interface{}) {
@ -154,6 +155,12 @@ func SkipUnlessExecProbeTimeoutEnabled() {
}
}
func SkipIfCSIMigrationEnabled() {
if utilfeature.DefaultFeatureGate.Enabled(csiMigration) {
skipInternalf(1, "Only supported when %v feature is disabled", csiMigration)
}
}
// SkipIfMissingResource skips if the gvr resource is missing.
func SkipIfMissingResource(dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, namespace string) {
resourceClient := dynamicClient.Resource(gvr).Namespace(namespace)

View File

@ -24,6 +24,7 @@ import (
"net"
"os"
"path/filepath"
"sync"
"time"
"github.com/onsi/gomega"
@ -48,6 +49,9 @@ const (
// singleCallTimeout is how long to try single API calls (like 'get' or 'list'). Used to prevent
// transient failures from failing tests.
singleCallTimeout = 5 * time.Minute
// sshBastionEnvKey is the environment variable key for running SSH commands via bastion.
sshBastionEnvKey = "KUBE_SSH_BASTION"
)
// GetSigner returns an ssh.Signer for the provider ("gce", etc.) that can be
@ -133,13 +137,45 @@ func NodeSSHHosts(c clientset.Interface) ([]string, error) {
len(hosts), len(nodelist.Items), nodelist)
}
sshHosts := make([]string, 0, len(hosts))
for _, h := range hosts {
sshHosts = append(sshHosts, net.JoinHostPort(h, SSHPort))
lenHosts := len(hosts)
wg := &sync.WaitGroup{}
wg.Add(lenHosts)
sshHosts := make([]string, 0, lenHosts)
var sshHostsLock sync.Mutex
for _, host := range hosts {
go func(host string) {
defer wg.Done()
if canConnect(host) {
e2elog.Logf("Assuming SSH on host %s", host)
sshHostsLock.Lock()
sshHosts = append(sshHosts, net.JoinHostPort(host, SSHPort))
sshHostsLock.Unlock()
} else {
e2elog.Logf("Skipping host %s because it does not run anything on port %s", host, SSHPort)
}
}(host)
}
wg.Wait()
return sshHosts, nil
}
// canConnect returns true if a network connection is possible to the SSHPort.
func canConnect(host string) bool {
if _, ok := os.LookupEnv(sshBastionEnvKey); ok {
return true
}
hostPort := net.JoinHostPort(host, SSHPort)
conn, err := net.DialTimeout("tcp", hostPort, 3*time.Second)
if err != nil {
e2elog.Logf("cannot dial %s: %v", hostPort, err)
return false
}
conn.Close()
return true
}
// Result holds the execution result of SSH command
type Result struct {
User string
@ -176,7 +212,7 @@ func SSH(cmd, host, provider string) (Result, error) {
result.User = os.Getenv("USER")
}
if bastion := os.Getenv("KUBE_SSH_BASTION"); len(bastion) > 0 {
if bastion := os.Getenv(sshBastionEnvKey); len(bastion) > 0 {
stdout, stderr, code, err := runSSHCommandViaBastion(cmd, result.User, bastion, host, signer)
result.Stdout = stdout
result.Stderr = stderr

View File

@ -19,6 +19,7 @@ package framework
import (
"crypto/rand"
"encoding/base64"
"errors"
"flag"
"fmt"
"io/ioutil"
@ -29,7 +30,6 @@ import (
"time"
"github.com/onsi/ginkgo/config"
"github.com/pkg/errors"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@ -182,6 +182,12 @@ type TestContextType struct {
// DockerConfigFile is a file that contains credentials which can be used to pull images from certain private registries, needed for a test.
DockerConfigFile string
// SnapshotControllerPodName is the name used for identifying the snapshot controller pod.
SnapshotControllerPodName string
// SnapshotControllerHTTPPort the port used for communicating with the snapshot controller HTTP endpoint.
SnapshotControllerHTTPPort int
}
// NodeKillerConfig describes configuration of NodeKiller -- a utility to
@ -223,6 +229,8 @@ type NodeTestContextType struct {
// the node e2e test. If empty, the default one (system.DefaultSpec) is
// used. The system specs are in test/e2e_node/system/specs/.
SystemSpecName string
// RestartKubelet restarts Kubelet unit when the process is killed.
RestartKubelet bool
// ExtraEnvs is a map of environment names to values.
ExtraEnvs map[string]string
}
@ -315,6 +323,9 @@ func RegisterCommonFlags(flags *flag.FlagSet) {
flags.StringVar(&TestContext.ProgressReportURL, "progress-report-url", "", "The URL to POST progress updates to as the suite runs to assist in aiding integrations. If empty, no messages sent.")
flags.StringVar(&TestContext.SpecSummaryOutput, "spec-dump", "", "The file to dump all ginkgo.SpecSummary to after tests run. If empty, no objects are saved/printed.")
flags.StringVar(&TestContext.DockerConfigFile, "docker-config-file", "", "A file that contains credentials which can be used to pull images from certain private registries, needed for a test.")
flags.StringVar(&TestContext.SnapshotControllerPodName, "snapshot-controller-pod-name", "", "The pod name to use for identifying the snapshot controller in the kube-system namespace.")
flags.IntVar(&TestContext.SnapshotControllerHTTPPort, "snapshot-controller-http-port", 0, "The port to use for snapshot controller HTTP communication.")
}
// RegisterClusterFlags registers flags specific to the cluster e2e test suite.
@ -473,7 +484,7 @@ func AfterReadingAllFlags(t *TestContextType) {
var err error
TestContext.CloudConfig.Provider, err = SetupProviderConfig(TestContext.Provider)
if err != nil {
if os.IsNotExist(errors.Cause(err)) {
if os.IsNotExist(errors.Unwrap(err)) {
// Provide a more helpful error message when the provider is unknown.
var providers []string
for _, name := range GetProviders() {

View File

@ -0,0 +1 @@
Hello World

View File

@ -25,13 +25,14 @@ limitations under the License.
package testfiles
import (
"embed"
"errors"
"fmt"
"io/fs"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"strings"
)
@ -147,32 +148,47 @@ func (r RootFileSource) DescribeFiles() string {
return description
}
// BindataFileSource handles files stored in a package generated with bindata.
type BindataFileSource struct {
Asset func(string) ([]byte, error)
AssetNames func() []string
// EmbeddedFileSource handles files stored in a package generated with bindata.
type EmbeddedFileSource struct {
EmbeddedFS embed.FS
Root string
fileList []string
}
// ReadTestFile looks for an asset with the given path.
func (b BindataFileSource) ReadTestFile(filePath string) ([]byte, error) {
fileBytes, err := b.Asset(filePath)
// ReadTestFile looks for an embedded file with the given path.
func (e EmbeddedFileSource) ReadTestFile(filepath string) ([]byte, error) {
relativePath := strings.TrimPrefix(filepath, fmt.Sprintf("%s/", e.Root))
b, err := e.EmbeddedFS.ReadFile(relativePath)
if err != nil {
// It would be nice to have a better way to detect
// "not found" errors :-/
if strings.HasSuffix(err.Error(), "not found") {
if errors.Is(err, fs.ErrNotExist) {
return nil, nil
}
return nil, err
}
return fileBytes, nil
return b, nil
}
// DescribeFiles explains about gobindata and then lists all available files.
func (b BindataFileSource) DescribeFiles() string {
// DescribeFiles explains that it is looking inside an embedded filesystem
func (e EmbeddedFileSource) DescribeFiles() string {
var lines []string
lines = append(lines, "The following files are built into the test executable via gobindata. For questions on maintaining gobindata, contact the sig-testing group.")
assets := b.AssetNames()
sort.Strings(assets)
lines = append(lines, assets...)
description := strings.Join(lines, "\n ")
return description
lines = append(lines, "The following files are embedded into the test executable:")
if len(e.fileList) == 0 {
e.populateFileList()
}
lines = append(lines, e.fileList...)
return strings.Join(lines, "\n\t")
}
func (e *EmbeddedFileSource) populateFileList() {
fs.WalkDir(e.EmbeddedFS, ".", func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() {
e.fileList = append(e.fileList, filepath.Join(e.Root, path))
}
return nil
})
}

View File

@ -20,19 +20,20 @@ import "time"
const (
// Default timeouts to be used in TimeoutContext
podStartTimeout = 5 * time.Minute
podStartShortTimeout = 2 * time.Minute
podStartSlowTimeout = 15 * time.Minute
podDeleteTimeout = 5 * time.Minute
claimProvisionTimeout = 5 * time.Minute
claimProvisionShortTimeout = 1 * time.Minute
claimBoundTimeout = 3 * time.Minute
pvReclaimTimeout = 3 * time.Minute
pvBoundTimeout = 3 * time.Minute
pvDeleteTimeout = 3 * time.Minute
pvDeleteSlowTimeout = 20 * time.Minute
snapshotCreateTimeout = 5 * time.Minute
snapshotDeleteTimeout = 5 * time.Minute
podStartTimeout = 5 * time.Minute
podStartShortTimeout = 2 * time.Minute
podStartSlowTimeout = 15 * time.Minute
podDeleteTimeout = 5 * time.Minute
claimProvisionTimeout = 5 * time.Minute
claimProvisionShortTimeout = 1 * time.Minute
claimBoundTimeout = 3 * time.Minute
pvReclaimTimeout = 3 * time.Minute
pvBoundTimeout = 3 * time.Minute
pvDeleteTimeout = 3 * time.Minute
pvDeleteSlowTimeout = 20 * time.Minute
snapshotCreateTimeout = 5 * time.Minute
snapshotDeleteTimeout = 5 * time.Minute
snapshotControllerMetricsTimeout = 5 * time.Minute
)
// TimeoutContext contains timeout settings for several actions.
@ -77,23 +78,27 @@ type TimeoutContext struct {
// SnapshotDelete is how long for snapshot to delete snapshotContent.
SnapshotDelete time.Duration
// SnapshotControllerMetrics is how long to wait for snapshot controller metrics.
SnapshotControllerMetrics time.Duration
}
// NewTimeoutContextWithDefaults returns a TimeoutContext with default values.
func NewTimeoutContextWithDefaults() *TimeoutContext {
return &TimeoutContext{
PodStart: podStartTimeout,
PodStartShort: podStartShortTimeout,
PodStartSlow: podStartSlowTimeout,
PodDelete: podDeleteTimeout,
ClaimProvision: claimProvisionTimeout,
ClaimProvisionShort: claimProvisionShortTimeout,
ClaimBound: claimBoundTimeout,
PVReclaim: pvReclaimTimeout,
PVBound: pvBoundTimeout,
PVDelete: pvDeleteTimeout,
PVDeleteSlow: pvDeleteSlowTimeout,
SnapshotCreate: snapshotCreateTimeout,
SnapshotDelete: snapshotDeleteTimeout,
PodStart: podStartTimeout,
PodStartShort: podStartShortTimeout,
PodStartSlow: podStartSlowTimeout,
PodDelete: podDeleteTimeout,
ClaimProvision: claimProvisionTimeout,
ClaimProvisionShort: claimProvisionShortTimeout,
ClaimBound: claimBoundTimeout,
PVReclaim: pvReclaimTimeout,
PVBound: pvBoundTimeout,
PVDelete: pvDeleteTimeout,
PVDeleteSlow: pvDeleteSlowTimeout,
SnapshotCreate: snapshotCreateTimeout,
SnapshotDelete: snapshotDeleteTimeout,
SnapshotControllerMetrics: snapshotControllerMetricsTimeout,
}
}

View File

@ -1,20 +1,11 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- saad-ali
- sig-storage-approvers
- rootfs
- gnufied
- jingxu97
- jsafrane
- msau42
reviewers:
- saad-ali
- rootfs
- gnufied
- jingxu97
- jsafrane
- msau42
- sig-storage-reviewers
- jeffvance
- copejon
- verult
- davidz627

View File

@ -368,11 +368,7 @@ func runVolumeTesterPod(client clientset.Interface, timeouts *framework.TimeoutC
var gracePeriod int64 = 1
var command string
if !framework.NodeOSDistroIs("windows") {
command = "while true ; do sleep 2; done "
} else {
command = "while(1) {sleep 2}"
}
command = "while true ; do sleep 2; done "
seLinuxOptions := &v1.SELinuxOptions{Level: "s0:c0,c1"}
clientPod := &v1.Pod{
TypeMeta: metav1.TypeMeta{
@ -572,47 +568,30 @@ func InjectContent(f *framework.Framework, config TestConfig, fsGroup *int64, fs
// generateWriteCmd is used by generateWriteBlockCmd and generateWriteFileCmd
func generateWriteCmd(content, path string) []string {
var commands []string
if !framework.NodeOSDistroIs("windows") {
commands = []string{"/bin/sh", "-c", "echo '" + content + "' > " + path}
} else {
commands = []string{"powershell", "/c", "echo '" + content + "' > " + path}
}
commands = []string{"/bin/sh", "-c", "echo '" + content + "' > " + path}
return commands
}
// generateReadBlockCmd generates the corresponding command lines to read from a block device with the given file path.
// Depending on the Node OS is Windows or linux, the command will use powershell or /bin/sh
func generateReadBlockCmd(fullPath string, numberOfCharacters int) []string {
var commands []string
if !framework.NodeOSDistroIs("windows") {
commands = []string{"head", "-c", strconv.Itoa(numberOfCharacters), fullPath}
} else {
// TODO: is there a way on windows to get the first X bytes from a device?
commands = []string{"powershell", "/c", "type " + fullPath}
}
commands = []string{"head", "-c", strconv.Itoa(numberOfCharacters), fullPath}
return commands
}
// generateWriteBlockCmd generates the corresponding command lines to write to a block device the given content.
// Depending on the Node OS is Windows or linux, the command will use powershell or /bin/sh
func generateWriteBlockCmd(content, fullPath string) []string {
return generateWriteCmd(content, fullPath)
}
// GenerateReadFileCmd generates the corresponding command lines to read from a file with the given file path.
// Depending on the Node OS is Windows or linux, the command will use powershell or /bin/sh
func GenerateReadFileCmd(fullPath string) []string {
var commands []string
if !framework.NodeOSDistroIs("windows") {
commands = []string{"cat", fullPath}
} else {
commands = []string{"powershell", "/c", "type " + fullPath}
}
commands = []string{"cat", fullPath}
return commands
}
// generateWriteFileCmd generates the corresponding command lines to write a file with the given content and file path.
// Depending on the Node OS is Windows or linux, the command will use powershell or /bin/sh
func generateWriteFileCmd(content, fullPath string) []string {
return generateWriteCmd(content, fullPath)
}
@ -638,9 +617,6 @@ func CheckVolumeModeOfPath(f *framework.Framework, pod *v1.Pod, volMode v1.Persi
// TODO: put this under e2epod once https://github.com/kubernetes/kubernetes/issues/81245
// is resolved. Otherwise there will be dependency issue.
func PodExec(f *framework.Framework, pod *v1.Pod, shExec string) (string, string, error) {
if framework.NodeOSDistroIs("windows") {
return f.ExecCommandInContainerWithFullOutput(pod.Name, pod.Spec.Containers[0].Name, "powershell", "/c", shExec)
}
return f.ExecCommandInContainerWithFullOutput(pod.Name, pod.Spec.Containers[0].Name, "/bin/sh", "-c", shExec)
}

View File

@ -33,8 +33,7 @@ import (
"regexp"
"strings"
"sync"
"github.com/pkg/errors"
"time"
v1 "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -57,11 +56,18 @@ type LogOutput struct {
// Matches harmless errors from pkg/kubelet/kubelet_pods.go.
var expectedErrors = regexp.MustCompile(`container .* in pod .* is (terminated|waiting to start|not available)|the server could not find the requested resource`)
// CopyAllLogs follows the logs of all containers in all pods,
// CopyPodLogs is basically CopyPodLogs for all current or future pods in the given namespace ns
func CopyAllLogs(ctx context.Context, cs clientset.Interface, ns string, to LogOutput) error {
return CopyPodLogs(ctx, cs, ns, "", to)
}
// CopyPodLogs follows the logs of all containers in pod with the given podName,
// including those that get created in the future, and writes each log
// line as configured in the output options. It does that until the
// context is done or until an error occurs.
//
// If podName is empty, it will follow all pods in the given namespace ns.
//
// Beware that there is currently no way to force log collection
// before removing pods, which means that there is a known race
// between "stop pod" and "collecting log entries". The alternative
@ -79,10 +85,17 @@ var expectedErrors = regexp.MustCompile(`container .* in pod .* is (terminated|w
// But it turned out to be rather confusing, so now a heuristic is used: if
// log output of a container was already captured, then capturing does not
// resume if the pod is marked for deletion.
func CopyAllLogs(ctx context.Context, cs clientset.Interface, ns string, to LogOutput) error {
watcher, err := cs.CoreV1().Pods(ns).Watch(context.TODO(), meta.ListOptions{})
func CopyPodLogs(ctx context.Context, cs clientset.Interface, ns, podName string, to LogOutput) error {
options := meta.ListOptions{}
if podName != "" {
options = meta.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s", podName),
}
}
watcher, err := cs.CoreV1().Pods(ns).Watch(context.TODO(), options)
if err != nil {
return errors.Wrap(err, "cannot create Pod event watcher")
return fmt.Errorf("cannot create Pod event watcher: %w", err)
}
go func() {
@ -96,7 +109,7 @@ func CopyAllLogs(ctx context.Context, cs clientset.Interface, ns string, to LogO
m.Lock()
defer m.Unlock()
pods, err := cs.CoreV1().Pods(ns).List(context.TODO(), meta.ListOptions{})
pods, err := cs.CoreV1().Pods(ns).List(context.TODO(), options)
if err != nil {
if to.StatusWriter != nil {
fmt.Fprintf(to.StatusWriter, "ERROR: get pod list in %s: %s\n", ns, err)
@ -252,18 +265,42 @@ func logsForPod(ctx context.Context, cs clientset.Interface, ns, pod string, opt
}
// WatchPods prints pod status events for a certain namespace or all namespaces
// when namespace name is empty.
func WatchPods(ctx context.Context, cs clientset.Interface, ns string, to io.Writer) error {
watcher, err := cs.CoreV1().Pods(ns).Watch(context.TODO(), meta.ListOptions{})
// when namespace name is empty. The closer can be nil if the caller doesn't want
// the file to be closed when watching stops.
func WatchPods(ctx context.Context, cs clientset.Interface, ns string, to io.Writer, toCloser io.Closer) (finalErr error) {
defer func() {
if finalErr != nil && toCloser != nil {
toCloser.Close()
}
}()
pods, err := cs.CoreV1().Pods(ns).Watch(context.Background(), meta.ListOptions{})
if err != nil {
return errors.Wrap(err, "cannot create Pod event watcher")
return fmt.Errorf("cannot create Pod watcher: %w", err)
}
defer func() {
if finalErr != nil {
pods.Stop()
}
}()
events, err := cs.CoreV1().Events(ns).Watch(context.Background(), meta.ListOptions{})
if err != nil {
return fmt.Errorf("cannot create Event watcher: %w", err)
}
go func() {
defer watcher.Stop()
defer func() {
pods.Stop()
events.Stop()
if toCloser != nil {
toCloser.Close()
}
}()
timeFormat := "15:04:05.000"
for {
select {
case e := <-watcher.ResultChan():
case e := <-pods.ResultChan():
if e.Object == nil {
continue
}
@ -274,7 +311,8 @@ func WatchPods(ctx context.Context, cs clientset.Interface, ns string, to io.Wri
}
buffer := new(bytes.Buffer)
fmt.Fprintf(buffer,
"pod event: %s: %s/%s %s: %s %s\n",
"%s pod: %s: %s/%s %s: %s %s\n",
time.Now().Format(timeFormat),
e.Type,
pod.Namespace,
pod.Name,
@ -300,7 +338,29 @@ func WatchPods(ctx context.Context, cs clientset.Interface, ns string, to io.Wri
fmt.Fprintf(buffer, "\n")
}
to.Write(buffer.Bytes())
case e := <-events.ResultChan():
if e.Object == nil {
continue
}
event, ok := e.Object.(*v1.Event)
if !ok {
continue
}
to.Write([]byte(fmt.Sprintf("%s event: %s/%s %s: %s %s: %s (%v - %v)\n",
time.Now().Format(timeFormat),
event.InvolvedObject.APIVersion,
event.InvolvedObject.Kind,
event.InvolvedObject.Name,
event.Source.Component,
event.Type,
event.Message,
event.FirstTimestamp,
event.LastTimestamp,
)))
case <-ctx.Done():
to.Write([]byte(fmt.Sprintf("%s ==== stopping pod watch ====\n",
time.Now().Format(timeFormat))))
return
}
}

View File

@ -20,10 +20,9 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@ -58,17 +57,17 @@ func LoadFromManifests(files ...string) ([]interface{}, error) {
// Ignore any additional fields for now, just determine what we have.
var what What
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), data, &what); err != nil {
return errors.Wrap(err, "decode TypeMeta")
return fmt.Errorf("decode TypeMeta: %w", err)
}
factory := factories[what]
if factory == nil {
return errors.Errorf("item of type %+v not supported", what)
return fmt.Errorf("item of type %+v not supported", what)
}
object := factory.New()
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), data, object); err != nil {
return errors.Wrapf(err, "decode %+v", what)
return fmt.Errorf("decode %+v: %w", what, err)
}
items = append(items, object)
return nil
@ -96,7 +95,7 @@ func visitManifests(cb func([]byte) error, files ...string) error {
for _, item := range items {
if err := cb(item); err != nil {
return errors.Wrap(err, fileName)
return fmt.Errorf("%s: %w", fileName, err)
}
}
}
@ -173,13 +172,13 @@ func CreateItems(f *framework.Framework, ns *v1.Namespace, items ...interface{})
if err == nil {
done = true
break
} else if errors.Cause(err) != errorItemNotSupported {
} else if !errors.Is(err, errorItemNotSupported) {
result = err
break
}
}
if result == nil && !done {
result = errors.Errorf("item of type %T not supported", item)
result = fmt.Errorf("item of type %T not supported", item)
break
}
}
@ -198,7 +197,7 @@ func CreateItems(f *framework.Framework, ns *v1.Namespace, items ...interface{})
func CreateFromManifests(f *framework.Framework, driverNamespace *v1.Namespace, patch func(item interface{}) error, files ...string) (func(), error) {
items, err := LoadFromManifests(files...)
if err != nil {
return nil, errors.Wrap(err, "CreateFromManifests")
return nil, fmt.Errorf("CreateFromManifests: %w", err)
}
if err := PatchItems(f, driverNamespace, items...); err != nil {
return nil, err
@ -337,21 +336,21 @@ func patchItemRecursively(f *framework.Framework, driverNamespace *v1.Namespace,
PatchName(f, &item.Name)
for i := range item.Subjects {
if err := patchItemRecursively(f, driverNamespace, &item.Subjects[i]); err != nil {
return errors.Wrapf(err, "%T", f)
return fmt.Errorf("%T: %w", f, err)
}
}
if err := patchItemRecursively(f, driverNamespace, &item.RoleRef); err != nil {
return errors.Wrapf(err, "%T", f)
return fmt.Errorf("%T: %w", f, err)
}
case *rbacv1.RoleBinding:
PatchNamespace(f, driverNamespace, &item.Namespace)
for i := range item.Subjects {
if err := patchItemRecursively(f, driverNamespace, &item.Subjects[i]); err != nil {
return errors.Wrapf(err, "%T", f)
return fmt.Errorf("%T: %w", f, err)
}
}
if err := patchItemRecursively(f, driverNamespace, &item.RoleRef); err != nil {
return errors.Wrapf(err, "%T", f)
return fmt.Errorf("%T: %w", f, err)
}
case *v1.Service:
PatchNamespace(f, driverNamespace, &item.ObjectMeta.Namespace)
@ -372,7 +371,7 @@ func patchItemRecursively(f *framework.Framework, driverNamespace *v1.Namespace,
return err
}
default:
return errors.Errorf("missing support for patching item of type %T", item)
return fmt.Errorf("missing support for patching item of type %T", item)
}
return nil
}
@ -395,7 +394,7 @@ func (*serviceAccountFactory) Create(f *framework.Framework, ns *v1.Namespace, i
}
client := f.ClientSet.CoreV1().ServiceAccounts(ns.Name)
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create ServiceAccount")
return nil, fmt.Errorf("create ServiceAccount: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -417,7 +416,7 @@ func (*clusterRoleFactory) Create(f *framework.Framework, ns *v1.Namespace, i in
framework.Logf("Define cluster role %v", item.GetName())
client := f.ClientSet.RbacV1().ClusterRoles()
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create ClusterRole")
return nil, fmt.Errorf("create ClusterRole: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -438,7 +437,7 @@ func (*clusterRoleBindingFactory) Create(f *framework.Framework, ns *v1.Namespac
client := f.ClientSet.RbacV1().ClusterRoleBindings()
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create ClusterRoleBinding")
return nil, fmt.Errorf("create ClusterRoleBinding: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -459,7 +458,7 @@ func (*roleFactory) Create(f *framework.Framework, ns *v1.Namespace, i interface
client := f.ClientSet.RbacV1().Roles(ns.Name)
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create Role")
return nil, fmt.Errorf("create Role: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -480,7 +479,7 @@ func (*roleBindingFactory) Create(f *framework.Framework, ns *v1.Namespace, i in
client := f.ClientSet.RbacV1().RoleBindings(ns.Name)
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create RoleBinding")
return nil, fmt.Errorf("create RoleBinding: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -501,7 +500,7 @@ func (*serviceFactory) Create(f *framework.Framework, ns *v1.Namespace, i interf
client := f.ClientSet.CoreV1().Services(ns.Name)
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create Service")
return nil, fmt.Errorf("create Service: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -522,7 +521,7 @@ func (*statefulSetFactory) Create(f *framework.Framework, ns *v1.Namespace, i in
client := f.ClientSet.AppsV1().StatefulSets(ns.Name)
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create StatefulSet")
return nil, fmt.Errorf("create StatefulSet: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -543,7 +542,7 @@ func (*daemonSetFactory) Create(f *framework.Framework, ns *v1.Namespace, i inte
client := f.ClientSet.AppsV1().DaemonSets(ns.Name)
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create DaemonSet")
return nil, fmt.Errorf("create DaemonSet: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -564,7 +563,7 @@ func (*storageClassFactory) Create(f *framework.Framework, ns *v1.Namespace, i i
client := f.ClientSet.StorageV1().StorageClasses()
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create StorageClass")
return nil, fmt.Errorf("create StorageClass: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -585,7 +584,7 @@ func (*csiDriverFactory) Create(f *framework.Framework, ns *v1.Namespace, i inte
client := f.ClientSet.StorageV1().CSIDrivers()
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create CSIDriver")
return nil, fmt.Errorf("create CSIDriver: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
@ -606,7 +605,7 @@ func (*secretFactory) Create(f *framework.Framework, ns *v1.Namespace, i interfa
client := f.ClientSet.CoreV1().Secrets(ns.Name)
if _, err := client.Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
return nil, errors.Wrap(err, "create Secret")
return nil, fmt.Errorf("create Secret: %w", err)
}
return func() error {
return client.Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})

View File

@ -19,6 +19,9 @@ package utils
import (
"context"
"fmt"
"io"
"os"
"path"
"regexp"
"strings"
"time"
@ -46,6 +49,8 @@ func StartPodLogs(f *framework.Framework, driverNamespace *v1.Namespace) func()
ns := driverNamespace.Name
podEventLog := ginkgo.GinkgoWriter
var podEventLogCloser io.Closer
to := podlogs.LogOutput{
StatusWriter: ginkgo.GinkgoWriter,
}
@ -69,17 +74,22 @@ func StartPodLogs(f *framework.Framework, driverNamespace *v1.Namespace) func()
// keeps each directory name smaller (the full test
// name at one point exceeded 256 characters, which was
// too much for some filesystems).
to.LogPathPrefix = framework.TestContext.ReportDir + "/" +
strings.Join(components, "/") + "/"
logDir := framework.TestContext.ReportDir + "/" + strings.Join(components, "/")
to.LogPathPrefix = logDir + "/"
err := os.MkdirAll(logDir, 0755)
framework.ExpectNoError(err, "create pod log directory")
f, err := os.Create(path.Join(logDir, "pod-event.log"))
framework.ExpectNoError(err, "create pod events log file")
podEventLog = f
podEventLogCloser = f
}
podlogs.CopyAllLogs(ctx, cs, ns, to)
// pod events are something that the framework already collects itself
// after a failed test. Logging them live is only useful for interactive
// debugging, not when we collect reports.
if framework.TestContext.ReportDir == "" {
podlogs.WatchPods(ctx, cs, ns, ginkgo.GinkgoWriter)
}
// The framework doesn't know about the driver pods because of
// the separate namespace. Therefore we always capture the
// events ourselves.
podlogs.WatchPods(ctx, cs, ns, podEventLog, podEventLogCloser)
return cancel
}

View File

@ -130,7 +130,6 @@ func GenerateSnapshotClassSpec(
snapshotter string,
parameters map[string]string,
ns string,
suffix string,
) *unstructured.Unstructured {
snapshotClass := &unstructured.Unstructured{
Object: map[string]interface{}{
@ -138,8 +137,7 @@ func GenerateSnapshotClassSpec(
"apiVersion": SnapshotAPIVersion,
"metadata": map[string]interface{}{
// Name must be unique, so let's base it on namespace name and use GenerateName
// TODO(#96234): Remove unnecessary suffix.
"name": names.SimpleNameGenerator.GenerateName(ns + "-" + suffix),
"name": names.SimpleNameGenerator.GenerateName(ns),
},
"driver": snapshotter,
"parameters": parameters,

View File

@ -24,6 +24,7 @@ import (
"math"
"math/rand"
"path/filepath"
"strconv"
"strings"
"time"
@ -571,6 +572,16 @@ func CheckWriteToPath(f *framework.Framework, pod *v1.Pod, volMode v1.Persistent
e2evolume.VerifyExecInPodSucceed(f, pod, fmt.Sprintf("echo %s | base64 -d | dd of=%s %s bs=%d count=1", encoded, pathForVolMode, oflag, len))
}
// GetSectorSize returns the sector size of the device.
func GetSectorSize(f *framework.Framework, pod *v1.Pod, device string) int {
stdout, _, err := e2evolume.PodExec(f, pod, fmt.Sprintf("blockdev --getss %s", device))
framework.ExpectNoError(err, "Failed to get sector size of %s", device)
ss, err := strconv.Atoi(stdout)
framework.ExpectNoError(err, "Sector size returned by blockdev command isn't integer value.")
return ss
}
// findMountPoints returns all mount points on given node under specified directory.
func findMountPoints(hostExec HostExec, node *v1.Node, dir string) []string {
result, err := hostExec.IssueCommandWithResult(fmt.Sprintf(`find %s -type d -exec mountpoint {} \; | grep 'is a mountpoint$' || true`, dir), node)

View File

@ -55,182 +55,182 @@ func RetryWithExponentialBackOff(fn wait.ConditionFunc) error {
func CreatePodWithRetries(c clientset.Interface, namespace string, obj *v1.Pod) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.CoreV1().Pods(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("Failed to create object with non-retriable error: %v ", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateRCWithRetries(c clientset.Interface, namespace string, obj *v1.ReplicationController) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.CoreV1().ReplicationControllers(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateReplicaSetWithRetries(c clientset.Interface, namespace string, obj *apps.ReplicaSet) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.AppsV1().ReplicaSets(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateDeploymentWithRetries(c clientset.Interface, namespace string, obj *apps.Deployment) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.AppsV1().Deployments(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateDaemonSetWithRetries(c clientset.Interface, namespace string, obj *apps.DaemonSet) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.AppsV1().DaemonSets(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateJobWithRetries(c clientset.Interface, namespace string, obj *batch.Job) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.BatchV1().Jobs(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateSecretWithRetries(c clientset.Interface, namespace string, obj *v1.Secret) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.CoreV1().Secrets(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateConfigMapWithRetries(c clientset.Interface, namespace string, obj *v1.ConfigMap) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.CoreV1().ConfigMaps(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateServiceWithRetries(c clientset.Interface, namespace string, obj *v1.Service) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.CoreV1().Services(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateStorageClassWithRetries(c clientset.Interface, obj *storage.StorageClass) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.StorageV1().StorageClasses().Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreateResourceQuotaWithRetries(c clientset.Interface, namespace string, obj *v1.ResourceQuota) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.CoreV1().ResourceQuotas(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreatePersistentVolumeWithRetries(c clientset.Interface, obj *v1.PersistentVolume) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.CoreV1().PersistentVolumes().Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}
func CreatePersistentVolumeClaimWithRetries(c clientset.Interface, namespace string, obj *v1.PersistentVolumeClaim) error {
if obj == nil {
return fmt.Errorf("Object provided to create is empty")
return fmt.Errorf("object provided to create is empty")
}
createFunc := func() (bool, error) {
_, err := c.CoreV1().PersistentVolumeClaims(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || apierrors.IsAlreadyExists(err) {
return true, nil
}
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to create object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(createFunc)
}

View File

@ -53,7 +53,7 @@ func deleteResource(c clientset.Interface, kind schema.GroupKind, namespace, nam
case api.Kind("Service"):
return c.CoreV1().Services(namespace).Delete(context.TODO(), name, options)
default:
return fmt.Errorf("Unsupported kind when deleting: %v", kind)
return fmt.Errorf("unsupported kind when deleting: %v", kind)
}
}
@ -63,7 +63,7 @@ func DeleteResourceWithRetries(c clientset.Interface, kind schema.GroupKind, nam
if err == nil || apierrors.IsNotFound(err) {
return true, nil
}
return false, fmt.Errorf("Failed to delete object with non-retriable error: %v", err)
return false, fmt.Errorf("failed to delete object with non-retriable error: %v", err)
}
return RetryWithExponentialBackOff(deleteFunc)
}

View File

@ -22,7 +22,6 @@ import (
"time"
"github.com/davecgh/go-spew/spew"
"github.com/pkg/errors"
apps "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
@ -201,7 +200,7 @@ func WaitForDeploymentRevisionAndImage(c clientset.Interface, ns, deploymentName
}
if err != nil {
if deployment == nil {
return errors.Wrapf(err, "error creating new replica set for deployment %q ", deploymentName)
return fmt.Errorf("error creating new replica set for deployment %q: %w", deploymentName, err)
}
deploymentImage := ""
if len(deployment.Spec.Template.Spec.Containers) > 0 {

View File

@ -25,23 +25,25 @@ import (
"regexp"
"strings"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"
)
// RegistryList holds public and private image registries
type RegistryList struct {
GcAuthenticatedRegistry string `yaml:"gcAuthenticatedRegistry"`
E2eRegistry string `yaml:"e2eRegistry"`
PromoterE2eRegistry string `yaml:"promoterE2eRegistry"`
BuildImageRegistry string `yaml:"buildImageRegistry"`
InvalidRegistry string `yaml:"invalidRegistry"`
GcEtcdRegistry string `yaml:"gcEtcdRegistry"`
GcRegistry string `yaml:"gcRegistry"`
SigStorageRegistry string `yaml:"sigStorageRegistry"`
GcrReleaseRegistry string `yaml:"gcrReleaseRegistry"`
PrivateRegistry string `yaml:"privateRegistry"`
SampleRegistry string `yaml:"sampleRegistry"`
MicrosoftRegistry string `yaml:"microsoftRegistry"`
GcAuthenticatedRegistry string `yaml:"gcAuthenticatedRegistry"`
E2eRegistry string `yaml:"e2eRegistry"`
PromoterE2eRegistry string `yaml:"promoterE2eRegistry"`
BuildImageRegistry string `yaml:"buildImageRegistry"`
InvalidRegistry string `yaml:"invalidRegistry"`
GcEtcdRegistry string `yaml:"gcEtcdRegistry"`
GcRegistry string `yaml:"gcRegistry"`
SigStorageRegistry string `yaml:"sigStorageRegistry"`
GcrReleaseRegistry string `yaml:"gcrReleaseRegistry"`
PrivateRegistry string `yaml:"privateRegistry"`
SampleRegistry string `yaml:"sampleRegistry"`
MicrosoftRegistry string `yaml:"microsoftRegistry"`
DockerLibraryRegistry string `yaml:"dockerLibraryRegistry"`
CloudProviderGcpRegistry string `yaml:"cloudProviderGcpRegistry"`
}
// Config holds an images registry, name, and version
@ -67,20 +69,8 @@ func (i *Config) SetVersion(version string) {
}
func initReg() RegistryList {
registry := RegistryList{
GcAuthenticatedRegistry: "gcr.io/authenticated-image-pulling",
E2eRegistry: "gcr.io/kubernetes-e2e-test-images",
PromoterE2eRegistry: "k8s.gcr.io/e2e-test-images",
BuildImageRegistry: "k8s.gcr.io/build-image",
InvalidRegistry: "invalid.com/invalid",
GcEtcdRegistry: "k8s.gcr.io",
GcRegistry: "k8s.gcr.io",
SigStorageRegistry: "k8s.gcr.io/sig-storage",
PrivateRegistry: "gcr.io/k8s-authenticated-test",
SampleRegistry: "gcr.io/google-samples",
GcrReleaseRegistry: "gcr.io/gke-release",
MicrosoftRegistry: "mcr.microsoft.com",
}
registry := initRegistry
repoList := os.Getenv("KUBE_TEST_REPO_LIST")
if repoList == "" {
return registry
@ -99,26 +89,27 @@ func initReg() RegistryList {
}
var (
initRegistry = RegistryList{
GcAuthenticatedRegistry: "gcr.io/authenticated-image-pulling",
E2eRegistry: "gcr.io/kubernetes-e2e-test-images",
PromoterE2eRegistry: "k8s.gcr.io/e2e-test-images",
BuildImageRegistry: "k8s.gcr.io/build-image",
InvalidRegistry: "invalid.com/invalid",
GcEtcdRegistry: "k8s.gcr.io",
GcRegistry: "k8s.gcr.io",
SigStorageRegistry: "k8s.gcr.io/sig-storage",
PrivateRegistry: "gcr.io/k8s-authenticated-test",
SampleRegistry: "gcr.io/google-samples",
GcrReleaseRegistry: "gcr.io/gke-release",
MicrosoftRegistry: "mcr.microsoft.com",
DockerLibraryRegistry: "docker.io/library",
CloudProviderGcpRegistry: "k8s.gcr.io/cloud-provider-gcp",
}
registry = initReg()
// PrivateRegistry is an image repository that requires authentication
PrivateRegistry = registry.PrivateRegistry
// Preconfigured image configs
dockerLibraryRegistry = "docker.io/library"
e2eRegistry = registry.E2eRegistry
promoterE2eRegistry = registry.PromoterE2eRegistry
buildImageRegistry = registry.BuildImageRegistry
gcAuthenticatedRegistry = registry.GcAuthenticatedRegistry
gcEtcdRegistry = registry.GcEtcdRegistry
gcRegistry = registry.GcRegistry
sigStorageRegistry = registry.SigStorageRegistry
gcrReleaseRegistry = registry.GcrReleaseRegistry
invalidRegistry = registry.InvalidRegistry
sampleRegistry = registry.SampleRegistry
microsoftRegistry = registry.MicrosoftRegistry
imageConfigs, originalImageConfigs = initImageConfigs()
imageConfigs, originalImageConfigs = initImageConfigs(registry)
)
const (
@ -211,51 +202,51 @@ const (
WindowsServer
)
func initImageConfigs() (map[int]Config, map[int]Config) {
func initImageConfigs(list RegistryList) (map[int]Config, map[int]Config) {
configs := map[int]Config{}
configs[Agnhost] = Config{promoterE2eRegistry, "agnhost", "2.32"}
configs[AgnhostPrivate] = Config{PrivateRegistry, "agnhost", "2.6"}
configs[AuthenticatedAlpine] = Config{gcAuthenticatedRegistry, "alpine", "3.7"}
configs[AuthenticatedWindowsNanoServer] = Config{gcAuthenticatedRegistry, "windows-nanoserver", "v1"}
configs[APIServer] = Config{promoterE2eRegistry, "sample-apiserver", "1.17.4"}
configs[AppArmorLoader] = Config{promoterE2eRegistry, "apparmor-loader", "1.3"}
configs[BusyBox] = Config{promoterE2eRegistry, "busybox", "1.29-1"}
configs[CheckMetadataConcealment] = Config{promoterE2eRegistry, "metadata-concealment", "1.6"}
configs[CudaVectorAdd] = Config{e2eRegistry, "cuda-vector-add", "1.0"}
configs[CudaVectorAdd2] = Config{promoterE2eRegistry, "cuda-vector-add", "2.2"}
configs[DebianIptables] = Config{buildImageRegistry, "debian-iptables", "buster-v1.6.2"}
configs[EchoServer] = Config{promoterE2eRegistry, "echoserver", "2.3"}
configs[Etcd] = Config{gcEtcdRegistry, "etcd", "3.4.13-0"}
configs[GlusterDynamicProvisioner] = Config{promoterE2eRegistry, "glusterdynamic-provisioner", "v1.0"}
configs[Httpd] = Config{promoterE2eRegistry, "httpd", "2.4.38-1"}
configs[HttpdNew] = Config{promoterE2eRegistry, "httpd", "2.4.39-1"}
configs[InvalidRegistryImage] = Config{invalidRegistry, "alpine", "3.1"}
configs[IpcUtils] = Config{promoterE2eRegistry, "ipc-utils", "1.2"}
configs[JessieDnsutils] = Config{promoterE2eRegistry, "jessie-dnsutils", "1.4"}
configs[Kitten] = Config{promoterE2eRegistry, "kitten", "1.4"}
configs[Nautilus] = Config{promoterE2eRegistry, "nautilus", "1.4"}
configs[NFSProvisioner] = Config{sigStorageRegistry, "nfs-provisioner", "v2.2.2"}
configs[Nginx] = Config{promoterE2eRegistry, "nginx", "1.14-1"}
configs[NginxNew] = Config{promoterE2eRegistry, "nginx", "1.15-1"}
configs[NodePerfNpbEp] = Config{promoterE2eRegistry, "node-perf/npb-ep", "1.1"}
configs[NodePerfNpbIs] = Config{promoterE2eRegistry, "node-perf/npb-is", "1.1"}
configs[NodePerfTfWideDeep] = Config{promoterE2eRegistry, "node-perf/tf-wide-deep", "1.1"}
configs[Nonewprivs] = Config{promoterE2eRegistry, "nonewprivs", "1.3"}
configs[NonRoot] = Config{promoterE2eRegistry, "nonroot", "1.1"}
configs[Agnhost] = Config{list.PromoterE2eRegistry, "agnhost", "2.32"}
configs[AgnhostPrivate] = Config{list.PrivateRegistry, "agnhost", "2.6"}
configs[AuthenticatedAlpine] = Config{list.GcAuthenticatedRegistry, "alpine", "3.7"}
configs[AuthenticatedWindowsNanoServer] = Config{list.GcAuthenticatedRegistry, "windows-nanoserver", "v1"}
configs[APIServer] = Config{list.PromoterE2eRegistry, "sample-apiserver", "1.17.4"}
configs[AppArmorLoader] = Config{list.PromoterE2eRegistry, "apparmor-loader", "1.3"}
configs[BusyBox] = Config{list.PromoterE2eRegistry, "busybox", "1.29-1"}
configs[CheckMetadataConcealment] = Config{list.PromoterE2eRegistry, "metadata-concealment", "1.6"}
configs[CudaVectorAdd] = Config{list.PromoterE2eRegistry, "cuda-vector-add", "1.0"}
configs[CudaVectorAdd2] = Config{list.PromoterE2eRegistry, "cuda-vector-add", "2.2"}
configs[DebianIptables] = Config{list.BuildImageRegistry, "debian-iptables", "buster-v1.6.5"}
configs[EchoServer] = Config{list.PromoterE2eRegistry, "echoserver", "2.3"}
configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.4.13-0"}
configs[GlusterDynamicProvisioner] = Config{list.PromoterE2eRegistry, "glusterdynamic-provisioner", "v1.0"}
configs[Httpd] = Config{list.PromoterE2eRegistry, "httpd", "2.4.38-1"}
configs[HttpdNew] = Config{list.PromoterE2eRegistry, "httpd", "2.4.39-1"}
configs[InvalidRegistryImage] = Config{list.InvalidRegistry, "alpine", "3.1"}
configs[IpcUtils] = Config{list.PromoterE2eRegistry, "ipc-utils", "1.2"}
configs[JessieDnsutils] = Config{list.PromoterE2eRegistry, "jessie-dnsutils", "1.4"}
configs[Kitten] = Config{list.PromoterE2eRegistry, "kitten", "1.4"}
configs[Nautilus] = Config{list.PromoterE2eRegistry, "nautilus", "1.4"}
configs[NFSProvisioner] = Config{list.SigStorageRegistry, "nfs-provisioner", "v2.2.2"}
configs[Nginx] = Config{list.PromoterE2eRegistry, "nginx", "1.14-1"}
configs[NginxNew] = Config{list.PromoterE2eRegistry, "nginx", "1.15-1"}
configs[NodePerfNpbEp] = Config{list.PromoterE2eRegistry, "node-perf/npb-ep", "1.1"}
configs[NodePerfNpbIs] = Config{list.PromoterE2eRegistry, "node-perf/npb-is", "1.1"}
configs[NodePerfTfWideDeep] = Config{list.PromoterE2eRegistry, "node-perf/tf-wide-deep", "1.1"}
configs[Nonewprivs] = Config{list.PromoterE2eRegistry, "nonewprivs", "1.3"}
configs[NonRoot] = Config{list.PromoterE2eRegistry, "nonroot", "1.1"}
// Pause - when these values are updated, also update cmd/kubelet/app/options/container_runtime.go
configs[Pause] = Config{gcRegistry, "pause", "3.4.1"}
configs[Perl] = Config{promoterE2eRegistry, "perl", "5.26"}
configs[PrometheusDummyExporter] = Config{gcRegistry, "prometheus-dummy-exporter", "v0.1.0"}
configs[PrometheusToSd] = Config{gcRegistry, "prometheus-to-sd", "v0.5.0"}
configs[Redis] = Config{promoterE2eRegistry, "redis", "5.0.5-alpine"}
configs[RegressionIssue74839] = Config{promoterE2eRegistry, "regression-issue-74839", "1.2"}
configs[ResourceConsumer] = Config{promoterE2eRegistry, "resource-consumer", "1.9"}
configs[SdDummyExporter] = Config{gcRegistry, "sd-dummy-exporter", "v0.2.0"}
configs[VolumeNFSServer] = Config{promoterE2eRegistry, "volume/nfs", "1.2"}
configs[VolumeISCSIServer] = Config{promoterE2eRegistry, "volume/iscsi", "2.2"}
configs[VolumeGlusterServer] = Config{promoterE2eRegistry, "volume/gluster", "1.2"}
configs[VolumeRBDServer] = Config{promoterE2eRegistry, "volume/rbd", "1.0.3"}
configs[WindowsServer] = Config{microsoftRegistry, "windows", "1809"}
configs[Pause] = Config{list.GcRegistry, "pause", "3.5"}
configs[Perl] = Config{list.PromoterE2eRegistry, "perl", "5.26"}
configs[PrometheusDummyExporter] = Config{list.GcRegistry, "prometheus-dummy-exporter", "v0.1.0"}
configs[PrometheusToSd] = Config{list.GcRegistry, "prometheus-to-sd", "v0.5.0"}
configs[Redis] = Config{list.PromoterE2eRegistry, "redis", "5.0.5-alpine"}
configs[RegressionIssue74839] = Config{list.PromoterE2eRegistry, "regression-issue-74839", "1.2"}
configs[ResourceConsumer] = Config{list.PromoterE2eRegistry, "resource-consumer", "1.9"}
configs[SdDummyExporter] = Config{list.GcRegistry, "sd-dummy-exporter", "v0.2.0"}
configs[VolumeNFSServer] = Config{list.PromoterE2eRegistry, "volume/nfs", "1.2"}
configs[VolumeISCSIServer] = Config{list.PromoterE2eRegistry, "volume/iscsi", "2.2"}
configs[VolumeGlusterServer] = Config{list.PromoterE2eRegistry, "volume/gluster", "1.2"}
configs[VolumeRBDServer] = Config{list.PromoterE2eRegistry, "volume/rbd", "1.0.3"}
configs[WindowsServer] = Config{list.MicrosoftRegistry, "windows", "1809"}
// if requested, map all the SHAs into a known format based on the input
originalImageConfigs := configs
@ -306,7 +297,7 @@ func getRepositoryMappedConfig(index int, config Config, repo string) Config {
h := sha256.New()
h.Write([]byte(pullSpec))
hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil)[:16])
hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))[:16]
shortName := reCharSafe.ReplaceAllLiteralString(pullSpec, "-")
shortName = reDashes.ReplaceAllLiteralString(shortName, "-")
@ -358,8 +349,15 @@ func GetPauseImageName() string {
return GetE2EImage(Pause)
}
// ReplaceRegistryInImageURL replaces the registry in the image URL with a custom one
// ReplaceRegistryInImageURL replaces the registry in the image URL with a custom one based
// on the configured registries.
func ReplaceRegistryInImageURL(imageURL string) (string, error) {
return replaceRegistryInImageURLWithList(imageURL, registry)
}
// replaceRegistryInImageURLWithList replaces the registry in the image URL with a custom one based
// on the given registry list.
func replaceRegistryInImageURLWithList(imageURL string, reg RegistryList) (string, error) {
parts := strings.Split(imageURL, "/")
countParts := len(parts)
registryAndUser := strings.Join(parts[:countParts-1], "/")
@ -373,6 +371,9 @@ func ReplaceRegistryInImageURL(imageURL string) (string, error) {
}
}
last := strings.SplitN(parts[countParts-1], ":", 2)
if len(last) == 1 {
return "", fmt.Errorf("image %q is required to be in an image:tag format", imageURL)
}
config := getRepositoryMappedConfig(index, Config{
registry: parts[0],
name: strings.Join([]string{strings.Join(parts[1:countParts-1], "/"), last[0]}, "/"),
@ -382,25 +383,37 @@ func ReplaceRegistryInImageURL(imageURL string) (string, error) {
}
switch registryAndUser {
case "gcr.io/kubernetes-e2e-test-images":
registryAndUser = e2eRegistry
case "k8s.gcr.io":
registryAndUser = gcRegistry
case "k8s.gcr.io/sig-storage":
registryAndUser = sigStorageRegistry
case "gcr.io/k8s-authenticated-test":
registryAndUser = PrivateRegistry
case "gcr.io/google-samples":
registryAndUser = sampleRegistry
case "gcr.io/gke-release":
registryAndUser = gcrReleaseRegistry
case "docker.io/library":
registryAndUser = dockerLibraryRegistry
case initRegistry.E2eRegistry:
registryAndUser = reg.E2eRegistry
case initRegistry.GcRegistry:
registryAndUser = reg.GcRegistry
case initRegistry.SigStorageRegistry:
registryAndUser = reg.SigStorageRegistry
case initRegistry.PrivateRegistry:
registryAndUser = reg.PrivateRegistry
case initRegistry.SampleRegistry:
registryAndUser = reg.SampleRegistry
case initRegistry.GcrReleaseRegistry:
registryAndUser = reg.GcrReleaseRegistry
case initRegistry.InvalidRegistry:
registryAndUser = reg.InvalidRegistry
case initRegistry.MicrosoftRegistry:
registryAndUser = reg.MicrosoftRegistry
case initRegistry.PromoterE2eRegistry:
registryAndUser = reg.PromoterE2eRegistry
case initRegistry.BuildImageRegistry:
registryAndUser = reg.BuildImageRegistry
case initRegistry.GcAuthenticatedRegistry:
registryAndUser = reg.GcAuthenticatedRegistry
case initRegistry.DockerLibraryRegistry:
registryAndUser = reg.DockerLibraryRegistry
case initRegistry.CloudProviderGcpRegistry:
registryAndUser = reg.CloudProviderGcpRegistry
default:
if countParts == 1 {
// We assume we found an image from docker hub library
// e.g. openjdk -> docker.io/library/openjdk
registryAndUser = dockerLibraryRegistry
registryAndUser = reg.DockerLibraryRegistry
break
}

View File

@ -23,12 +23,11 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math"
"math/big"
"time"
"github.com/pkg/errors"
certutil "k8s.io/client-go/util/cert"
)
@ -59,10 +58,10 @@ func NewSignedCert(cfg *certutil.Config, key crypto.Signer, caCert *x509.Certifi
return nil, err
}
if len(cfg.CommonName) == 0 {
return nil, errors.New("must specify a CommonName")
return nil, fmt.Errorf("must specify a CommonName")
}
if len(cfg.Usages) == 0 {
return nil, errors.New("must specify at least one ExtKeyUsage")
return nil, fmt.Errorf("must specify at least one ExtKeyUsage")
}
certTmpl := x509.Certificate{

View File

@ -80,7 +80,7 @@ func WaitUntilPodIsScheduled(c clientset.Interface, name, namespace string, time
return p, nil
}
}
return nil, fmt.Errorf("Timed out after %v when waiting for pod %v/%v to start.", timeout, namespace, name)
return nil, fmt.Errorf("timed out after %v when waiting for pod %v/%v to start", timeout, namespace, name)
}
func RunPodAndGetNodeName(c clientset.Interface, pod *v1.Pod, timeout time.Duration) (string, error) {
@ -357,7 +357,7 @@ func (config *DeploymentConfig) create() error {
config.applyTo(&deployment.Spec.Template)
if err := CreateDeploymentWithRetries(config.Client, config.Namespace, deployment); err != nil {
return fmt.Errorf("Error creating deployment: %v", err)
return fmt.Errorf("error creating deployment: %v", err)
}
config.RCConfigLog("Created deployment with name: %v, namespace: %v, replica count: %v", deployment.Name, config.Namespace, removePtr(deployment.Spec.Replicas))
return nil
@ -435,7 +435,7 @@ func (config *ReplicaSetConfig) create() error {
config.applyTo(&rs.Spec.Template)
if err := CreateReplicaSetWithRetries(config.Client, config.Namespace, rs); err != nil {
return fmt.Errorf("Error creating replica set: %v", err)
return fmt.Errorf("error creating replica set: %v", err)
}
config.RCConfigLog("Created replica set with name: %v, namespace: %v, replica count: %v", rs.Name, config.Namespace, removePtr(rs.Spec.Replicas))
return nil
@ -509,7 +509,7 @@ func (config *JobConfig) create() error {
config.applyTo(&job.Spec.Template)
if err := CreateJobWithRetries(config.Client, config.Namespace, job); err != nil {
return fmt.Errorf("Error creating job: %v", err)
return fmt.Errorf("error creating job: %v", err)
}
config.RCConfigLog("Created job with name: %v, namespace: %v, parallelism/completions: %v", job.Name, config.Namespace, job.Spec.Parallelism)
return nil
@ -628,7 +628,7 @@ func (config *RCConfig) create() error {
config.applyTo(rc.Spec.Template)
if err := CreateRCWithRetries(config.Client, config.Namespace, rc); err != nil {
return fmt.Errorf("Error creating replication controller: %v", err)
return fmt.Errorf("error creating replication controller: %v", err)
}
config.RCConfigLog("Created replication controller with name: %v, namespace: %v, replica count: %v", rc.Name, config.Namespace, removePtr(rc.Spec.Replicas))
return nil
@ -850,7 +850,7 @@ func (config *RCConfig) start() error {
} else {
config.RCConfigLog("Can't list pod debug info: %v", err)
}
return fmt.Errorf("Only %d pods started out of %d", oldRunning, config.Replicas)
return fmt.Errorf("only %d pods started out of %d", oldRunning, config.Replicas)
}
return nil
}
@ -880,7 +880,7 @@ func StartPods(c clientset.Interface, replicas int, namespace string, podNamePre
label := labels.SelectorFromSet(labels.Set(map[string]string{"startPodsID": startPodsID}))
err := WaitForPodsWithLabelRunning(c, namespace, label)
if err != nil {
return fmt.Errorf("Error waiting for %d pods to be running - probably a timeout: %v", replicas, err)
return fmt.Errorf("error waiting for %d pods to be running - probably a timeout: %v", replicas, err)
}
}
return nil
@ -920,7 +920,7 @@ func WaitForEnoughPodsWithLabelRunning(c clientset.Interface, ns string, label l
break
}
if !running {
return fmt.Errorf("Timeout while waiting for pods with labels %q to be running", label.String())
return fmt.Errorf("timeout while waiting for pods with labels %q to be running", label.String())
}
return nil
}
@ -1194,12 +1194,12 @@ func DoPrepareNode(client clientset.Interface, node *v1.Node, strategy PrepareNo
break
}
if !apierrors.IsConflict(err) {
return fmt.Errorf("Error while applying patch %v to Node %v: %v", string(patch), node.Name, err)
return fmt.Errorf("error while applying patch %v to Node %v: %v", string(patch), node.Name, err)
}
time.Sleep(100 * time.Millisecond)
}
if err != nil {
return fmt.Errorf("Too many conflicts when applying patch %v to Node %v: %s", string(patch), node.Name, err)
return fmt.Errorf("too many conflicts when applying patch %v to Node %v: %s", string(patch), node.Name, err)
}
for attempt := 0; attempt < retries; attempt++ {
@ -1207,12 +1207,12 @@ func DoPrepareNode(client clientset.Interface, node *v1.Node, strategy PrepareNo
break
}
if !apierrors.IsConflict(err) {
return fmt.Errorf("Error while preparing objects for node %s: %s", node.Name, err)
return fmt.Errorf("error while preparing objects for node %s: %s", node.Name, err)
}
time.Sleep(100 * time.Millisecond)
}
if err != nil {
return fmt.Errorf("Too many conflicts when creating objects for node %s: %s", node.Name, err)
return fmt.Errorf("too many conflicts when creating objects for node %s: %s", node.Name, err)
}
return nil
}
@ -1220,9 +1220,10 @@ func DoPrepareNode(client clientset.Interface, node *v1.Node, strategy PrepareNo
func DoCleanupNode(client clientset.Interface, nodeName string, strategy PrepareNodeStrategy) error {
var err error
for attempt := 0; attempt < retries; attempt++ {
node, err := client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
var node *v1.Node
node, err = client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("Skipping cleanup of Node: failed to get Node %v: %v", nodeName, err)
return fmt.Errorf("skipping cleanup of Node: failed to get Node %v: %v", nodeName, err)
}
updatedNode := strategy.CleanupNode(node)
if apiequality.Semantic.DeepEqual(node, updatedNode) {
@ -1232,12 +1233,12 @@ func DoCleanupNode(client clientset.Interface, nodeName string, strategy Prepare
break
}
if !apierrors.IsConflict(err) {
return fmt.Errorf("Error when updating Node %v: %v", nodeName, err)
return fmt.Errorf("error when updating Node %v: %v", nodeName, err)
}
time.Sleep(100 * time.Millisecond)
}
if err != nil {
return fmt.Errorf("Too many conflicts when trying to cleanup Node %v: %s", nodeName, err)
return fmt.Errorf("too many conflicts when trying to cleanup Node %v: %s", nodeName, err)
}
for attempt := 0; attempt < retries; attempt++ {
@ -1246,12 +1247,12 @@ func DoCleanupNode(client clientset.Interface, nodeName string, strategy Prepare
break
}
if !apierrors.IsConflict(err) {
return fmt.Errorf("Error when cleaning up Node %v objects: %v", nodeName, err)
return fmt.Errorf("error when cleaning up Node %v objects: %v", nodeName, err)
}
time.Sleep(100 * time.Millisecond)
}
if err != nil {
return fmt.Errorf("Too many conflicts when trying to cleanup Node %v objects: %s", nodeName, err)
return fmt.Errorf("too many conflicts when trying to cleanup Node %v objects: %s", nodeName, err)
}
return nil
}
@ -1303,7 +1304,7 @@ func MakePodSpec() v1.PodSpec {
return v1.PodSpec{
Containers: []v1.Container{{
Name: "pause",
Image: "k8s.gcr.io/pause:3.4.1",
Image: "k8s.gcr.io/pause:3.5",
Ports: []v1.ContainerPort{{ContainerPort: 80}},
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
@ -1321,7 +1322,7 @@ func MakePodSpec() v1.PodSpec {
func makeCreatePod(client clientset.Interface, namespace string, podTemplate *v1.Pod) error {
if err := CreatePodWithRetries(client, namespace, podTemplate); err != nil {
return fmt.Errorf("Error creating pod: %v", err)
return fmt.Errorf("error creating pod: %v", err)
}
return nil
}
@ -1450,7 +1451,7 @@ func createController(client clientset.Interface, controllerName, namespace stri
},
}
if err := CreateRCWithRetries(client, namespace, rc); err != nil {
return fmt.Errorf("Error creating replication controller: %v", err)
return fmt.Errorf("error creating replication controller: %v", err)
}
return nil
}
@ -1557,7 +1558,7 @@ func (config *SecretConfig) Run() error {
}
if err := CreateSecretWithRetries(config.Client, config.Namespace, secret); err != nil {
return fmt.Errorf("Error creating secret: %v", err)
return fmt.Errorf("error creating secret: %v", err)
}
config.LogFunc("Created secret %v/%v", config.Namespace, config.Name)
return nil
@ -1565,7 +1566,7 @@ func (config *SecretConfig) Run() error {
func (config *SecretConfig) Stop() error {
if err := DeleteResourceWithRetries(config.Client, api.Kind("Secret"), config.Namespace, config.Name, metav1.DeleteOptions{}); err != nil {
return fmt.Errorf("Error deleting secret: %v", err)
return fmt.Errorf("error deleting secret: %v", err)
}
config.LogFunc("Deleted secret %v/%v", config.Namespace, config.Name)
return nil
@ -1615,7 +1616,7 @@ func (config *ConfigMapConfig) Run() error {
}
if err := CreateConfigMapWithRetries(config.Client, config.Namespace, configMap); err != nil {
return fmt.Errorf("Error creating configmap: %v", err)
return fmt.Errorf("error creating configmap: %v", err)
}
config.LogFunc("Created configmap %v/%v", config.Namespace, config.Name)
return nil
@ -1623,7 +1624,7 @@ func (config *ConfigMapConfig) Run() error {
func (config *ConfigMapConfig) Stop() error {
if err := DeleteResourceWithRetries(config.Client, api.Kind("ConfigMap"), config.Namespace, config.Name, metav1.DeleteOptions{}); err != nil {
return fmt.Errorf("Error deleting configmap: %v", err)
return fmt.Errorf("error deleting configmap: %v", err)
}
config.LogFunc("Deleted configmap %v/%v", config.Namespace, config.Name)
return nil
@ -1725,7 +1726,7 @@ type DaemonConfig struct {
func (config *DaemonConfig) Run() error {
if config.Image == "" {
config.Image = "k8s.gcr.io/pause:3.4.1"
config.Image = "k8s.gcr.io/pause:3.5"
}
nameLabel := map[string]string{
"name": config.Name + "-daemon",
@ -1752,7 +1753,7 @@ func (config *DaemonConfig) Run() error {
}
if err := CreateDaemonSetWithRetries(config.Client, config.Namespace, daemon); err != nil {
return fmt.Errorf("Error creating daemonset: %v", err)
return fmt.Errorf("error creating daemonset: %v", err)
}
var nodes *v1.NodeList
@ -1763,7 +1764,7 @@ func (config *DaemonConfig) Run() error {
if err == nil {
break
} else if i+1 == retries {
return fmt.Errorf("Error listing Nodes while waiting for DaemonSet %v: %v", config.Name, err)
return fmt.Errorf("error listing Nodes while waiting for DaemonSet %v: %v", config.Name, err)
}
}

View File

@ -55,7 +55,7 @@ func ScaleResourceWithRetries(scalesGetter scaleclient.ScalesGetter, namespace,
err = scale.WaitForScaleHasDesiredReplicas(scalesGetter, gvr.GroupResource(), name, namespace, size, waitForReplicas)
}
if err != nil {
return fmt.Errorf("Error while scaling %s to %d replicas: %v", name, size, err)
return fmt.Errorf("error while scaling %s to %d replicas: %v", name, size, err)
}
return nil
}