mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 02:33:34 +00:00
rebase: update kubernetes to v1.25.0
update kubernetes to latest v1.25.0 release. Signed-off-by: Madhu Rajanna <madhupr007@gmail.com>
This commit is contained in:
committed by
mergify[bot]
parent
f47839d73d
commit
e3bf375035
8
vendor/k8s.io/kubernetes/pkg/api/v1/pod/util.go
generated
vendored
8
vendor/k8s.io/kubernetes/pkg/api/v1/pod/util.go
generated
vendored
@ -23,8 +23,6 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// FindPort locates the container port for the given pod and portName. If the
|
||||
@ -68,11 +66,7 @@ const AllContainers ContainerType = (InitContainers | Containers | EphemeralCont
|
||||
// AllFeatureEnabledContainers returns a ContainerType mask which includes all container
|
||||
// types except for the ones guarded by feature gate.
|
||||
func AllFeatureEnabledContainers() ContainerType {
|
||||
containerType := AllContainers
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||
containerType &= ^EphemeralContainers
|
||||
}
|
||||
return containerType
|
||||
return AllContainers
|
||||
}
|
||||
|
||||
// ContainerVisitor is called with each container spec, and returns true
|
||||
|
8
vendor/k8s.io/kubernetes/pkg/apis/apps/types.go
generated
vendored
8
vendor/k8s.io/kubernetes/pkg/apis/apps/types.go
generated
vendored
@ -27,8 +27,9 @@ import (
|
||||
|
||||
// StatefulSet represents a set of pods with consistent identities.
|
||||
// Identities are defined as:
|
||||
// - Network: A single stable DNS and hostname.
|
||||
// - Storage: As many VolumeClaims as requested.
|
||||
// - Network: A single stable DNS and hostname.
|
||||
// - Storage: As many VolumeClaims as requested.
|
||||
//
|
||||
// The StatefulSet guarantees that a given network identity will always
|
||||
// map to the same storage identity.
|
||||
type StatefulSet struct {
|
||||
@ -206,7 +207,6 @@ type StatefulSetSpec struct {
|
||||
// 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
|
||||
|
||||
@ -256,7 +256,6 @@ type StatefulSetStatus struct {
|
||||
Conditions []StatefulSetCondition
|
||||
|
||||
// Total number of available pods (ready for at least minReadySeconds) targeted by this statefulset.
|
||||
// This is a beta field and requires enabling StatefulSetMinReadySeconds feature gate.
|
||||
// +optional
|
||||
AvailableReplicas int32
|
||||
}
|
||||
@ -627,7 +626,6 @@ 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 beta field and enabled/disabled by DaemonSetUpdateSurge feature gate.
|
||||
// +optional
|
||||
MaxSurge intstr.IntOrString
|
||||
}
|
||||
|
18
vendor/k8s.io/kubernetes/pkg/apis/autoscaling/helpers.go
generated
vendored
18
vendor/k8s.io/kubernetes/pkg/apis/autoscaling/helpers.go
generated
vendored
@ -21,16 +21,16 @@ package autoscaling
|
||||
// It should always be called when converting internal -> external versions, prior
|
||||
// to setting any of the custom annotations:
|
||||
//
|
||||
// annotations, copiedAnnotations := DropRoundTripHorizontalPodAutoscalerAnnotations(externalObj.Annotations)
|
||||
// externalObj.Annotations = annotations
|
||||
// annotations, copiedAnnotations := DropRoundTripHorizontalPodAutoscalerAnnotations(externalObj.Annotations)
|
||||
// externalObj.Annotations = annotations
|
||||
//
|
||||
// if internal.SomeField != nil {
|
||||
// if !copiedAnnotations {
|
||||
// externalObj.Annotations = DeepCopyStringMap(externalObj.Annotations)
|
||||
// copiedAnnotations = true
|
||||
// }
|
||||
// externalObj.Annotations[...] = json.Marshal(...)
|
||||
// }
|
||||
// if internal.SomeField != nil {
|
||||
// if !copiedAnnotations {
|
||||
// externalObj.Annotations = DeepCopyStringMap(externalObj.Annotations)
|
||||
// copiedAnnotations = true
|
||||
// }
|
||||
// externalObj.Annotations[...] = json.Marshal(...)
|
||||
// }
|
||||
func DropRoundTripHorizontalPodAutoscalerAnnotations(in map[string]string) (out map[string]string, copied bool) {
|
||||
_, hasMetricsSpecs := in[MetricSpecsAnnotation]
|
||||
_, hasBehaviorSpecs := in[BehaviorSpecsAnnotation]
|
||||
|
142
vendor/k8s.io/kubernetes/pkg/apis/batch/types.go
generated
vendored
142
vendor/k8s.io/kubernetes/pkg/apis/batch/types.go
generated
vendored
@ -110,6 +110,119 @@ const (
|
||||
IndexedCompletion CompletionMode = "Indexed"
|
||||
)
|
||||
|
||||
// PodFailurePolicyAction specifies how a Pod failure is handled.
|
||||
// +enum
|
||||
type PodFailurePolicyAction string
|
||||
|
||||
const (
|
||||
// This is an action which might be taken on a pod failure - mark the
|
||||
// pod's job as Failed and terminate all running pods.
|
||||
PodFailurePolicyActionFailJob PodFailurePolicyAction = "FailJob"
|
||||
|
||||
// This is an action which might be taken on a pod failure - the counter towards
|
||||
// .backoffLimit, represented by the job's .status.failed field, is not
|
||||
// incremented and a replacement pod is created.
|
||||
PodFailurePolicyActionIgnore PodFailurePolicyAction = "Ignore"
|
||||
|
||||
// This is an action which might be taken on a pod failure - the pod failure
|
||||
// is handled in the default way - the counter towards .backoffLimit,
|
||||
// represented by the job's .status.failed field, is incremented.
|
||||
PodFailurePolicyActionCount PodFailurePolicyAction = "Count"
|
||||
)
|
||||
|
||||
// +enum
|
||||
type PodFailurePolicyOnExitCodesOperator string
|
||||
|
||||
const (
|
||||
PodFailurePolicyOnExitCodesOpIn PodFailurePolicyOnExitCodesOperator = "In"
|
||||
PodFailurePolicyOnExitCodesOpNotIn PodFailurePolicyOnExitCodesOperator = "NotIn"
|
||||
)
|
||||
|
||||
// PodFailurePolicyOnExitCodesRequirement describes the requirement for handling
|
||||
// a failed pod based on its container exit codes. In particular, it lookups the
|
||||
// .state.terminated.exitCode for each app container and init container status,
|
||||
// represented by the .status.containerStatuses and .status.initContainerStatuses
|
||||
// fields in the Pod status, respectively. Containers completed with success
|
||||
// (exit code 0) are excluded from the requirement check.
|
||||
type PodFailurePolicyOnExitCodesRequirement struct {
|
||||
// Restricts the check for exit codes to the container with the
|
||||
// specified name. When null, the rule applies to all containers.
|
||||
// When specified, it should match one the container or initContainer
|
||||
// names in the pod template.
|
||||
// +optional
|
||||
ContainerName *string
|
||||
|
||||
// Represents the relationship between the container exit code(s) and the
|
||||
// specified values. Containers completed with success (exit code 0) are
|
||||
// excluded from the requirement check. Possible values are:
|
||||
// - In: the requirement is satisfied if at least one container exit code
|
||||
// (might be multiple if there are multiple containers not restricted
|
||||
// by the 'containerName' field) is in the set of specified values.
|
||||
// - NotIn: the requirement is satisfied if at least one container exit code
|
||||
// (might be multiple if there are multiple containers not restricted
|
||||
// by the 'containerName' field) is not in the set of specified values.
|
||||
// Additional values are considered to be added in the future. Clients should
|
||||
// react to an unknown operator by assuming the requirement is not satisfied.
|
||||
Operator PodFailurePolicyOnExitCodesOperator
|
||||
|
||||
// Specifies the set of values. Each returned container exit code (might be
|
||||
// multiple in case of multiple containers) is checked against this set of
|
||||
// values with respect to the operator. The list of values must be ordered
|
||||
// and must not contain duplicates. Value '0' cannot be used for the In operator.
|
||||
// At least one element is required. At most 255 elements are allowed.
|
||||
// +listType=set
|
||||
Values []int32
|
||||
}
|
||||
|
||||
// PodFailurePolicyOnPodConditionsPattern describes a pattern for matching
|
||||
// an actual pod condition type.
|
||||
type PodFailurePolicyOnPodConditionsPattern struct {
|
||||
// Specifies the required Pod condition type. To match a pod condition
|
||||
// it is required that specified type equals the pod condition type.
|
||||
Type api.PodConditionType
|
||||
// Specifies the required Pod condition status. To match a pod condition
|
||||
// it is required that the specified status equals the pod condition status.
|
||||
// Defaults to True.
|
||||
Status api.ConditionStatus
|
||||
}
|
||||
|
||||
// PodFailurePolicyRule describes how a pod failure is handled when the requirements are met.
|
||||
// One of OnExitCodes and onPodConditions, but not both, can be used in each rule.
|
||||
type PodFailurePolicyRule struct {
|
||||
// Specifies the action taken on a pod failure when the requirements are satisfied.
|
||||
// Possible values are:
|
||||
// - FailJob: indicates that the pod's job is marked as Failed and all
|
||||
// running pods are terminated.
|
||||
// - Ignore: indicates that the counter towards the .backoffLimit is not
|
||||
// incremented and a replacement pod is created.
|
||||
// - Count: indicates that the pod is handled in the default way - the
|
||||
// counter towards the .backoffLimit is incremented.
|
||||
// Additional values are considered to be added in the future. Clients should
|
||||
// react to an unknown action by skipping the rule.
|
||||
Action PodFailurePolicyAction
|
||||
|
||||
// Represents the requirement on the container exit codes.
|
||||
// +optional
|
||||
OnExitCodes *PodFailurePolicyOnExitCodesRequirement
|
||||
|
||||
// Represents the requirement on the pod conditions. The requirement is represented
|
||||
// as a list of pod condition patterns. The requirement is satisfied if at
|
||||
// least one pattern matches an actual pod condition. At most 20 elements are allowed.
|
||||
// +listType=atomic
|
||||
OnPodConditions []PodFailurePolicyOnPodConditionsPattern
|
||||
}
|
||||
|
||||
// PodFailurePolicy describes how failed pods influence the backoffLimit.
|
||||
type PodFailurePolicy struct {
|
||||
// A list of pod failure policy rules. The rules are evaluated in order.
|
||||
// Once a rule matches a Pod failure, the remaining of the rules are ignored.
|
||||
// When no rule matches the Pod failure, the default handling applies - the
|
||||
// counter of pod failures is incremented and it is checked against
|
||||
// the backoffLimit. At most 20 elements are allowed.
|
||||
// +listType=atomic
|
||||
Rules []PodFailurePolicyRule
|
||||
}
|
||||
|
||||
// JobSpec describes how the job execution will look like.
|
||||
type JobSpec struct {
|
||||
|
||||
@ -128,6 +241,19 @@ type JobSpec struct {
|
||||
// +optional
|
||||
Completions *int32
|
||||
|
||||
// Specifies the policy of handling failed pods. In particular, it allows to
|
||||
// specify the set of actions and conditions which need to be
|
||||
// satisfied to take the associated action.
|
||||
// If empty, the default behaviour applies - the counter of failed pods,
|
||||
// represented by the jobs's .status.failed field, is incremented and it is
|
||||
// checked against the backoffLimit. This field cannot be used in combination
|
||||
// with .spec.podTemplate.spec.restartPolicy=OnFailure.
|
||||
//
|
||||
// This field is alpha-level. To use this field, you must enable the
|
||||
// `JobPodFailurePolicy` feature gate (disabled by default).
|
||||
// +optional
|
||||
PodFailurePolicy *PodFailurePolicy
|
||||
|
||||
// Specifies the duration in seconds relative to the startTime that the job
|
||||
// may be continuously active before the system tries to terminate it; value
|
||||
// must be positive integer. If a Job is suspended (at creation or through an
|
||||
@ -313,6 +439,9 @@ const (
|
||||
JobComplete JobConditionType = "Complete"
|
||||
// JobFailed means the job has failed its execution.
|
||||
JobFailed JobConditionType = "Failed"
|
||||
// FailureTarget means the job is about to fail its execution.
|
||||
// The constant is to be renamed once the name is accepted within the KEP-3329.
|
||||
AlphaNoCompatGuaranteeJobFailureTarget JobConditionType = "FailureTarget"
|
||||
)
|
||||
|
||||
// JobCondition describes current state of a job.
|
||||
@ -376,9 +505,16 @@ type CronJobSpec struct {
|
||||
// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
|
||||
Schedule string
|
||||
|
||||
// The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones.
|
||||
// If not specified, this will rely on the time zone of the kube-controller-manager process.
|
||||
// ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.
|
||||
// The time zone name for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones.
|
||||
// If not specified, this will default to the time zone of the kube-controller-manager process.
|
||||
// The set of valid time zone names and the time zone offset is loaded from the system-wide time zone
|
||||
// database by the API server during CronJob validation and the controller manager during execution.
|
||||
// If no system-wide time zone database can be found a bundled version of the database is used instead.
|
||||
// If the time zone name becomes invalid during the lifetime of a CronJob or due to a change in host
|
||||
// configuration, the controller will stop creating new new Jobs and will create a system event with the
|
||||
// reason UnknownTimeZone.
|
||||
// More information can be found in https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones
|
||||
// This is beta field and must be enabled via the `CronJobTimeZone` feature gate.
|
||||
// +optional
|
||||
TimeZone *string
|
||||
|
||||
|
96
vendor/k8s.io/kubernetes/pkg/apis/batch/zz_generated.deepcopy.go
generated
vendored
96
vendor/k8s.io/kubernetes/pkg/apis/batch/zz_generated.deepcopy.go
generated
vendored
@ -252,6 +252,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) {
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodFailurePolicy != nil {
|
||||
in, out := &in.PodFailurePolicy, &out.PodFailurePolicy
|
||||
*out = new(PodFailurePolicy)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ActiveDeadlineSeconds != nil {
|
||||
in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds
|
||||
*out = new(int64)
|
||||
@ -387,6 +392,97 @@ func (in *JobTemplateSpec) DeepCopy() *JobTemplateSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodFailurePolicy) DeepCopyInto(out *PodFailurePolicy) {
|
||||
*out = *in
|
||||
if in.Rules != nil {
|
||||
in, out := &in.Rules, &out.Rules
|
||||
*out = make([]PodFailurePolicyRule, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodFailurePolicy.
|
||||
func (in *PodFailurePolicy) DeepCopy() *PodFailurePolicy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodFailurePolicy)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodFailurePolicyOnExitCodesRequirement) DeepCopyInto(out *PodFailurePolicyOnExitCodesRequirement) {
|
||||
*out = *in
|
||||
if in.ContainerName != nil {
|
||||
in, out := &in.ContainerName, &out.ContainerName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Values != nil {
|
||||
in, out := &in.Values, &out.Values
|
||||
*out = make([]int32, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodFailurePolicyOnExitCodesRequirement.
|
||||
func (in *PodFailurePolicyOnExitCodesRequirement) DeepCopy() *PodFailurePolicyOnExitCodesRequirement {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodFailurePolicyOnExitCodesRequirement)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodFailurePolicyOnPodConditionsPattern) DeepCopyInto(out *PodFailurePolicyOnPodConditionsPattern) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodFailurePolicyOnPodConditionsPattern.
|
||||
func (in *PodFailurePolicyOnPodConditionsPattern) DeepCopy() *PodFailurePolicyOnPodConditionsPattern {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodFailurePolicyOnPodConditionsPattern)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PodFailurePolicyRule) DeepCopyInto(out *PodFailurePolicyRule) {
|
||||
*out = *in
|
||||
if in.OnExitCodes != nil {
|
||||
in, out := &in.OnExitCodes, &out.OnExitCodes
|
||||
*out = new(PodFailurePolicyOnExitCodesRequirement)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.OnPodConditions != nil {
|
||||
in, out := &in.OnPodConditions, &out.OnPodConditions
|
||||
*out = make([]PodFailurePolicyOnPodConditionsPattern, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodFailurePolicyRule.
|
||||
func (in *PodFailurePolicyRule) DeepCopy() *PodFailurePolicyRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodFailurePolicyRule)
|
||||
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
|
||||
|
67
vendor/k8s.io/kubernetes/pkg/apis/core/helper/helpers.go
generated
vendored
67
vendor/k8s.io/kubernetes/pkg/apis/core/helper/helpers.go
generated
vendored
@ -27,7 +27,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
@ -361,72 +360,6 @@ func ContainsAccessMode(modes []core.PersistentVolumeAccessMode, mode core.Persi
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement core type into a struct that implements
|
||||
// labels.Selector.
|
||||
func NodeSelectorRequirementsAsSelector(nsm []core.NodeSelectorRequirement) (labels.Selector, error) {
|
||||
if len(nsm) == 0 {
|
||||
return labels.Nothing(), nil
|
||||
}
|
||||
selector := labels.NewSelector()
|
||||
for _, expr := range nsm {
|
||||
var op selection.Operator
|
||||
switch expr.Operator {
|
||||
case core.NodeSelectorOpIn:
|
||||
op = selection.In
|
||||
case core.NodeSelectorOpNotIn:
|
||||
op = selection.NotIn
|
||||
case core.NodeSelectorOpExists:
|
||||
op = selection.Exists
|
||||
case core.NodeSelectorOpDoesNotExist:
|
||||
op = selection.DoesNotExist
|
||||
case core.NodeSelectorOpGt:
|
||||
op = selection.GreaterThan
|
||||
case core.NodeSelectorOpLt:
|
||||
op = selection.LessThan
|
||||
default:
|
||||
return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator)
|
||||
}
|
||||
r, err := labels.NewRequirement(expr.Key, op, expr.Values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selector = selector.Add(*r)
|
||||
}
|
||||
return selector, nil
|
||||
}
|
||||
|
||||
// NodeSelectorRequirementsAsFieldSelector converts the []NodeSelectorRequirement core type into a struct that implements
|
||||
// fields.Selector.
|
||||
func NodeSelectorRequirementsAsFieldSelector(nsm []core.NodeSelectorRequirement) (fields.Selector, error) {
|
||||
if len(nsm) == 0 {
|
||||
return fields.Nothing(), nil
|
||||
}
|
||||
|
||||
selectors := []fields.Selector{}
|
||||
for _, expr := range nsm {
|
||||
switch expr.Operator {
|
||||
case core.NodeSelectorOpIn:
|
||||
if len(expr.Values) != 1 {
|
||||
return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q",
|
||||
len(expr.Values), expr.Operator)
|
||||
}
|
||||
selectors = append(selectors, fields.OneTermEqualSelector(expr.Key, expr.Values[0]))
|
||||
|
||||
case core.NodeSelectorOpNotIn:
|
||||
if len(expr.Values) != 1 {
|
||||
return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q",
|
||||
len(expr.Values), expr.Operator)
|
||||
}
|
||||
selectors = append(selectors, fields.OneTermNotEqualSelector(expr.Key, expr.Values[0]))
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("%q is not a valid node field selector operator", expr.Operator)
|
||||
}
|
||||
}
|
||||
|
||||
return fields.AndSelectors(selectors...), nil
|
||||
}
|
||||
|
||||
// GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations
|
||||
// and converts it to the []Toleration type in core.
|
||||
func GetTolerationsFromPodAnnotations(annotations map[string]string) ([]core.Toleration, error) {
|
||||
|
12
vendor/k8s.io/kubernetes/pkg/apis/core/pods/helpers.go
generated
vendored
12
vendor/k8s.io/kubernetes/pkg/apis/core/pods/helpers.go
generated
vendored
@ -20,9 +20,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/fieldpath"
|
||||
)
|
||||
|
||||
@ -47,12 +45,10 @@ func VisitContainersWithPath(podSpec *api.PodSpec, specPath *field.Path, visitor
|
||||
return false
|
||||
}
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||
fldPath = specPath.Child("ephemeralContainers")
|
||||
for i := range podSpec.EphemeralContainers {
|
||||
if !visitor((*api.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon), fldPath.Index(i)) {
|
||||
return false
|
||||
}
|
||||
fldPath = specPath.Child("ephemeralContainers")
|
||||
for i := range podSpec.EphemeralContainers {
|
||||
if !visitor((*api.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon), fldPath.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
189
vendor/k8s.io/kubernetes/pkg/apis/core/types.go
generated
vendored
189
vendor/k8s.io/kubernetes/pkg/apis/core/types.go
generated
vendored
@ -154,7 +154,7 @@ type VolumeSource struct {
|
||||
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
|
||||
// +optional
|
||||
StorageOS *StorageOSVolumeSource
|
||||
// CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).
|
||||
// CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers.
|
||||
// +optional
|
||||
CSI *CSIVolumeSource
|
||||
// Ephemeral represents a volume that is handled by a cluster storage driver.
|
||||
@ -1716,11 +1716,20 @@ type CSIPersistentVolumeSource struct {
|
||||
// ControllerExpandSecretRef is a reference to the secret object containing
|
||||
// sensitive information to pass to the CSI driver to complete the CSI
|
||||
// ControllerExpandVolume call.
|
||||
// This is an alpha field and requires enabling ExpandCSIVolumes feature gate.
|
||||
// This is an beta field and requires enabling ExpandCSIVolumes feature gate.
|
||||
// This field is optional, and may be empty if no secret is required. If the
|
||||
// secret object contains more than one secret, all secrets are passed.
|
||||
// +optional
|
||||
ControllerExpandSecretRef *SecretReference
|
||||
|
||||
// NodeExpandSecretRef is a reference to the secret object containing
|
||||
// sensitive information to pass to the CSI driver to complete the CSI
|
||||
// NodeExpandVolume call.
|
||||
// This is an alpha field and requires enabling CSINodeExpandSecret feature gate.
|
||||
// This field is optional, may be omitted if no secret is required. If the
|
||||
// secret object contains more than one secret, all secrets are passed.
|
||||
// +optional
|
||||
NodeExpandSecretRef *SecretReference
|
||||
}
|
||||
|
||||
// CSIVolumeSource represents a source location of a volume to mount, managed by an external CSI driver
|
||||
@ -2421,6 +2430,10 @@ const (
|
||||
PodReasonUnschedulable = "Unschedulable"
|
||||
// ContainersReady indicates whether all containers in the pod are ready.
|
||||
ContainersReady PodConditionType = "ContainersReady"
|
||||
// AlphaNoCompatGuaranteeDisruptionTarget indicates the pod is about to be deleted due to a
|
||||
// disruption (such as preemption, eviction API or garbage-collection).
|
||||
// The constant is to be renamed once the name is accepted within the KEP-3329.
|
||||
AlphaNoCompatGuaranteeDisruptionTarget PodConditionType = "DisruptionTarget"
|
||||
)
|
||||
|
||||
// PodCondition represents pod's condition
|
||||
@ -2820,7 +2833,6 @@ type PodSpec struct {
|
||||
// pod to perform user-initiated actions such as debugging. This list cannot be specified when
|
||||
// creating a pod, and it cannot be modified by updating the pod spec. In order to add an
|
||||
// ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource.
|
||||
// This field is beta-level and available on clusters that haven't disabled the EphemeralContainers feature gate.
|
||||
// +optional
|
||||
EphemeralContainers []EphemeralContainer
|
||||
// +optional
|
||||
@ -2964,6 +2976,7 @@ type PodSpec struct {
|
||||
// If the OS field is set to windows, following fields must be unset:
|
||||
// - spec.hostPID
|
||||
// - spec.hostIPC
|
||||
// - spec.hostUsers
|
||||
// - spec.securityContext.seLinuxOptions
|
||||
// - spec.securityContext.seccompProfile
|
||||
// - spec.securityContext.fsGroup
|
||||
@ -2983,7 +2996,6 @@ type PodSpec struct {
|
||||
// - spec.containers[*].securityContext.runAsUser
|
||||
// - spec.containers[*].securityContext.runAsGroup
|
||||
// +optional
|
||||
// This is a beta field and requires the IdentifyPodOS feature
|
||||
OS *PodOS
|
||||
}
|
||||
|
||||
@ -3067,6 +3079,18 @@ type PodSecurityContext struct {
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
ShareProcessNamespace *bool
|
||||
// Use the host's user namespace.
|
||||
// Optional: Default to true.
|
||||
// If set to true or not present, the pod will be run in the host user namespace, useful
|
||||
// for when the pod needs a feature only available to the host user namespace, such as
|
||||
// loading a kernel module with CAP_SYS_MODULE.
|
||||
// When set to false, a new user namespace is created for the pod. Setting false is useful
|
||||
// for mitigating container breakout vulnerabilities even allowing users to run their
|
||||
// containers as root without actually having root privileges on the host.
|
||||
// Note that this field cannot be set when spec.os.name is windows.
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
HostUsers *bool
|
||||
// The SELinux context to be applied to all containers.
|
||||
// If unspecified, the container runtime will allocate a random SELinux context for each
|
||||
// container. May also be set in SecurityContext. If set in
|
||||
@ -3213,8 +3237,9 @@ type PodDNSConfigOption struct {
|
||||
|
||||
// PodIP represents the IP address of a pod.
|
||||
// IP address information. Each entry includes:
|
||||
// IP: An IP address allocated to the pod. Routable at least within
|
||||
// the cluster.
|
||||
//
|
||||
// IP: An IP address allocated to the pod. Routable at least within
|
||||
// the cluster.
|
||||
type PodIP struct {
|
||||
IP string
|
||||
}
|
||||
@ -3317,8 +3342,6 @@ var _ = Container(EphemeralContainerCommon{})
|
||||
//
|
||||
// To add an ephemeral container, use the ephemeralcontainers subresource of an existing
|
||||
// Pod. Ephemeral containers may not be removed or restarted.
|
||||
//
|
||||
// This is a beta feature available on clusters that haven't disabled the EphemeralContainers feature gate.
|
||||
type EphemeralContainer struct {
|
||||
// Ephemeral containers have all of the fields of Container, plus additional fields
|
||||
// specific to ephemeral containers. Fields in common with Container are in the
|
||||
@ -3381,7 +3404,6 @@ type PodStatus struct {
|
||||
ContainerStatuses []ContainerStatus
|
||||
|
||||
// Status for any ephemeral containers that have run in this pod.
|
||||
// This field is beta-level and available on clusters that haven't disabled the EphemeralContainers feature gate.
|
||||
// +optional
|
||||
EphemeralContainerStatuses []ContainerStatus
|
||||
}
|
||||
@ -3641,27 +3663,33 @@ const (
|
||||
ServiceTypeExternalName ServiceType = "ExternalName"
|
||||
)
|
||||
|
||||
// ServiceInternalTrafficPolicyType describes the type of traffic routing for
|
||||
// internal traffic
|
||||
// ServiceInternalTrafficPolicyType describes the endpoint-selection policy for
|
||||
// traffic sent to the ClusterIP.
|
||||
type ServiceInternalTrafficPolicyType string
|
||||
|
||||
const (
|
||||
// ServiceInternalTrafficPolicyCluster routes traffic to all endpoints
|
||||
// ServiceInternalTrafficPolicyCluster routes traffic to all endpoints.
|
||||
ServiceInternalTrafficPolicyCluster ServiceInternalTrafficPolicyType = "Cluster"
|
||||
|
||||
// ServiceInternalTrafficPolicyLocal only routes to node-local
|
||||
// endpoints, otherwise drops the traffic
|
||||
// ServiceInternalTrafficPolicyLocal routes traffic only to endpoints on the same
|
||||
// node as the traffic was received on (dropping the traffic if there are no
|
||||
// local endpoints).
|
||||
ServiceInternalTrafficPolicyLocal ServiceInternalTrafficPolicyType = "Local"
|
||||
)
|
||||
|
||||
// ServiceExternalTrafficPolicyType string
|
||||
// ServiceExternalTrafficPolicyType describes the endpoint-selection policy for
|
||||
// traffic to external service entrypoints (NodePorts, ExternalIPs, and
|
||||
// LoadBalancer IPs).
|
||||
type ServiceExternalTrafficPolicyType string
|
||||
|
||||
const (
|
||||
// ServiceExternalTrafficPolicyTypeLocal specifies node-local endpoints behavior.
|
||||
ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local"
|
||||
// ServiceExternalTrafficPolicyTypeCluster specifies cluster-wide (legacy) behavior.
|
||||
// ServiceExternalTrafficPolicyTypeCluster routes traffic to all endpoints.
|
||||
ServiceExternalTrafficPolicyTypeCluster ServiceExternalTrafficPolicyType = "Cluster"
|
||||
|
||||
// ServiceExternalTrafficPolicyTypeLocal preserves the source IP of the traffic by
|
||||
// routing only to endpoints on the same node as the traffic was received on
|
||||
// (dropping the traffic if there are no local endpoints).
|
||||
ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local"
|
||||
)
|
||||
|
||||
// These are the valid conditions of a service.
|
||||
@ -3721,27 +3749,27 @@ const (
|
||||
IPv6Protocol IPFamily = "IPv6"
|
||||
)
|
||||
|
||||
// IPFamilyPolicyType represents the dual-stack-ness requested or required by a Service
|
||||
type IPFamilyPolicyType string
|
||||
// IPFamilyPolicy represents the dual-stack-ness requested or required by a Service
|
||||
type IPFamilyPolicy string
|
||||
|
||||
const (
|
||||
// IPFamilyPolicySingleStack indicates that this service is required to have a single IPFamily.
|
||||
// The IPFamily assigned is based on the default IPFamily used by the cluster
|
||||
// or as identified by service.spec.ipFamilies field
|
||||
IPFamilyPolicySingleStack IPFamilyPolicyType = "SingleStack"
|
||||
IPFamilyPolicySingleStack IPFamilyPolicy = "SingleStack"
|
||||
// IPFamilyPolicyPreferDualStack indicates that this service prefers dual-stack when
|
||||
// the cluster is configured for dual-stack. If the cluster is not configured
|
||||
// for dual-stack the service will be assigned a single IPFamily. If the IPFamily is not
|
||||
// set in service.spec.ipFamilies then the service will be assigned the default IPFamily
|
||||
// configured on the cluster
|
||||
IPFamilyPolicyPreferDualStack IPFamilyPolicyType = "PreferDualStack"
|
||||
IPFamilyPolicyPreferDualStack IPFamilyPolicy = "PreferDualStack"
|
||||
// IPFamilyPolicyRequireDualStack indicates that this service requires dual-stack. Using
|
||||
// IPFamilyPolicyRequireDualStack on a single stack cluster will result in validation errors. The
|
||||
// IPFamilies (and their order) assigned to this service is based on service.spec.ipFamilies. If
|
||||
// service.spec.ipFamilies was not provided then it will be assigned according to how they are
|
||||
// configured on the cluster. If service.spec.ipFamilies has only one entry then the alternative
|
||||
// IPFamily will be added by apiserver
|
||||
IPFamilyPolicyRequireDualStack IPFamilyPolicyType = "RequireDualStack"
|
||||
IPFamilyPolicyRequireDualStack IPFamilyPolicy = "RequireDualStack"
|
||||
)
|
||||
|
||||
// ServiceSpec describes the attributes that a user creates on a service
|
||||
@ -3814,7 +3842,7 @@ type ServiceSpec struct {
|
||||
// to this service can be controlled by service.spec.ipFamilies and service.spec.clusterIPs
|
||||
// respectively.
|
||||
// +optional
|
||||
IPFamilyPolicy *IPFamilyPolicyType
|
||||
IPFamilyPolicy *IPFamilyPolicy
|
||||
|
||||
// ExternalName is the external reference that kubedns or equivalent will
|
||||
// return as a CNAME record for this service. No proxying will be involved.
|
||||
@ -3853,12 +3881,19 @@ type ServiceSpec struct {
|
||||
// +optional
|
||||
LoadBalancerSourceRanges []string
|
||||
|
||||
// externalTrafficPolicy denotes if this Service desires to route external
|
||||
// traffic to node-local or cluster-wide endpoints. "Local" preserves the
|
||||
// client source IP and avoids a second hop for LoadBalancer and Nodeport
|
||||
// type services, but risks potentially imbalanced traffic spreading.
|
||||
// "Cluster" obscures the client source IP and may cause a second hop to
|
||||
// another node, but should have good overall load-spreading.
|
||||
// externalTrafficPolicy describes how nodes distribute service traffic they
|
||||
// receive on one of the Service's "externally-facing" addresses (NodePorts,
|
||||
// ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will configure
|
||||
// the service in a way that assumes that external load balancers will take care
|
||||
// of balancing the service traffic between nodes, and so each node will deliver
|
||||
// traffic only to the node-local endpoints of the service, without masquerading
|
||||
// the client source IP. (Traffic mistakenly sent to a node with no endpoints will
|
||||
// be dropped.) The default value, "Cluster", uses the standard behavior of
|
||||
// routing to all endpoints evenly (possibly modified by topology and other
|
||||
// features). Note that traffic sent to an External IP or LoadBalancer IP from
|
||||
// within the cluster will always get "Cluster" semantics, but clients sending to
|
||||
// a NodePort from within the cluster may need to take traffic policy into account
|
||||
// when picking a node.
|
||||
// +optional
|
||||
ExternalTrafficPolicy ServiceExternalTrafficPolicyType
|
||||
|
||||
@ -3905,12 +3940,12 @@ type ServiceSpec struct {
|
||||
// +optional
|
||||
LoadBalancerClass *string
|
||||
|
||||
// InternalTrafficPolicy specifies if the cluster internal traffic
|
||||
// should be routed to all endpoints or node-local endpoints only.
|
||||
// "Cluster" routes internal traffic to a Service to all endpoints.
|
||||
// "Local" routes traffic to node-local endpoints only, traffic is
|
||||
// dropped if no node-local endpoints are ready.
|
||||
// The default value is "Cluster".
|
||||
// InternalTrafficPolicy describes how nodes distribute service traffic they
|
||||
// receive on the ClusterIP. If set to "Local", the proxy will assume that pods
|
||||
// only want to talk to endpoints of the service on the same node as the pod,
|
||||
// dropping the traffic if there are no local endpoints. The default value,
|
||||
// "Cluster", uses the standard behavior of routing to all endpoints evenly
|
||||
// (possibly modified by topology and other features).
|
||||
// +featureGate=ServiceInternalTrafficPolicy
|
||||
// +optional
|
||||
InternalTrafficPolicy *ServiceInternalTrafficPolicyType
|
||||
@ -4014,17 +4049,18 @@ type ServiceAccountList struct {
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Endpoints is a collection of endpoints that implement the actual service. Example:
|
||||
// Name: "mysvc",
|
||||
// Subsets: [
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
|
||||
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
|
||||
// },
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.3.3"}],
|
||||
// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
|
||||
// },
|
||||
// ]
|
||||
//
|
||||
// Name: "mysvc",
|
||||
// Subsets: [
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
|
||||
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
|
||||
// },
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.3.3"}],
|
||||
// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
|
||||
// },
|
||||
// ]
|
||||
type Endpoints struct {
|
||||
metav1.TypeMeta
|
||||
// +optional
|
||||
@ -4037,13 +4073,16 @@ type Endpoints struct {
|
||||
// EndpointSubset is a group of addresses with a common set of ports. The
|
||||
// expanded set of endpoints is the Cartesian product of Addresses x Ports.
|
||||
// For example, given:
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
|
||||
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
|
||||
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
|
||||
// }
|
||||
//
|
||||
// The resulting set of endpoints can be viewed as:
|
||||
// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ],
|
||||
// b: [ 10.10.1.1:309, 10.10.2.2:309 ]
|
||||
//
|
||||
// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ],
|
||||
// b: [ 10.10.1.1:309, 10.10.2.2:309 ]
|
||||
type EndpointSubset struct {
|
||||
Addresses []EndpointAddress
|
||||
NotReadyAddresses []EndpointAddress
|
||||
@ -5594,6 +5633,17 @@ const (
|
||||
ScheduleAnyway UnsatisfiableConstraintAction = "ScheduleAnyway"
|
||||
)
|
||||
|
||||
// NodeInclusionPolicy defines the type of node inclusion policy
|
||||
// +enum
|
||||
type NodeInclusionPolicy string
|
||||
|
||||
const (
|
||||
// NodeInclusionPolicyIgnore means ignore this scheduling directive when calculating pod topology spread skew.
|
||||
NodeInclusionPolicyIgnore NodeInclusionPolicy = "Ignore"
|
||||
// NodeInclusionPolicyHonor means use this scheduling directive when calculating pod topology spread skew.
|
||||
NodeInclusionPolicyHonor NodeInclusionPolicy = "Honor"
|
||||
)
|
||||
|
||||
// TopologySpreadConstraint specifies how to spread matching pods among the given topology.
|
||||
type TopologySpreadConstraint struct {
|
||||
// MaxSkew describes the degree to which pods may be unevenly distributed.
|
||||
@ -5622,7 +5672,8 @@ type TopologySpreadConstraint struct {
|
||||
// We consider each <key, value> as a "bucket", and try to put balanced number
|
||||
// of pods into each bucket.
|
||||
// We define a domain as a particular instance of a topology.
|
||||
// Also, we define an eligible domain as a domain whose nodes match the node selector.
|
||||
// Also, we define an eligible domain as a domain whose nodes meet the requirements of
|
||||
// nodeAffinityPolicy and nodeTaintsPolicy.
|
||||
// e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology.
|
||||
// And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology.
|
||||
// It's a required field.
|
||||
@ -5677,9 +5728,37 @@ type TopologySpreadConstraint struct {
|
||||
// because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones,
|
||||
// it will violate MaxSkew.
|
||||
//
|
||||
// This is an alpha field and requires enabling MinDomainsInPodTopologySpread feature gate.
|
||||
// This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default).
|
||||
// +optional
|
||||
MinDomains *int32
|
||||
// NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector
|
||||
// when calculating pod topology spread skew. Options are:
|
||||
// - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations.
|
||||
// - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations.
|
||||
//
|
||||
// If this value is nil, the behavior is equivalent to the Honor policy.
|
||||
// This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.
|
||||
// +optional
|
||||
NodeAffinityPolicy *NodeInclusionPolicy
|
||||
// NodeTaintsPolicy indicates how we will treat node taints when calculating
|
||||
// pod topology spread skew. Options are:
|
||||
// - Honor: nodes without taints, along with tainted nodes for which the incoming pod
|
||||
// has a toleration, are included.
|
||||
// - Ignore: node taints are ignored. All nodes are included.
|
||||
//
|
||||
// If this value is nil, the behavior is equivalent to the Ignore policy.
|
||||
// This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag.
|
||||
// +optional
|
||||
NodeTaintsPolicy *NodeInclusionPolicy
|
||||
// MatchLabelKeys is a set of pod label keys to select the pods over which
|
||||
// spreading will be calculated. The keys are used to lookup values from the
|
||||
// incoming pod labels, those key-value labels are ANDed with labelSelector
|
||||
// to select the group of existing pods over which spreading will be calculated
|
||||
// for the incoming pod. Keys that don't exist in the incoming pod labels will
|
||||
// be ignored. A null or empty list means only match against labelSelector.
|
||||
// +listType=atomic
|
||||
// +optional
|
||||
MatchLabelKeys []string
|
||||
}
|
||||
|
||||
// These are the built-in errors for PortStatus.
|
||||
|
2
vendor/k8s.io/kubernetes/pkg/apis/core/v1/conversion.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/apis/core/v1/conversion.go
generated
vendored
@ -303,6 +303,7 @@ func Convert_core_PodSpec_To_v1_PodSpec(in *core.PodSpec, out *v1.PodSpec, s con
|
||||
out.HostNetwork = in.SecurityContext.HostNetwork
|
||||
out.HostIPC = in.SecurityContext.HostIPC
|
||||
out.ShareProcessNamespace = in.SecurityContext.ShareProcessNamespace
|
||||
out.HostUsers = in.SecurityContext.HostUsers
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -358,6 +359,7 @@ func Convert_v1_PodSpec_To_core_PodSpec(in *v1.PodSpec, out *core.PodSpec, s con
|
||||
out.SecurityContext.HostPID = in.HostPID
|
||||
out.SecurityContext.HostIPC = in.HostIPC
|
||||
out.SecurityContext.ShareProcessNamespace = in.ShareProcessNamespace
|
||||
out.SecurityContext.HostUsers = in.HostUsers
|
||||
|
||||
return nil
|
||||
}
|
||||
|
59
vendor/k8s.io/kubernetes/pkg/apis/core/v1/helper/helpers.go
generated
vendored
59
vendor/k8s.io/kubernetes/pkg/apis/core/v1/helper/helpers.go
generated
vendored
@ -370,3 +370,62 @@ func ScopedResourceSelectorRequirementsAsSelector(ssr v1.ScopedResourceSelectorR
|
||||
selector = selector.Add(*r)
|
||||
return selector, nil
|
||||
}
|
||||
|
||||
// nodeSelectorRequirementsAsLabelRequirements converts the NodeSelectorRequirement
|
||||
// type to a labels.Requirement type.
|
||||
func nodeSelectorRequirementsAsLabelRequirements(nsr v1.NodeSelectorRequirement) (*labels.Requirement, error) {
|
||||
var op selection.Operator
|
||||
switch nsr.Operator {
|
||||
case v1.NodeSelectorOpIn:
|
||||
op = selection.In
|
||||
case v1.NodeSelectorOpNotIn:
|
||||
op = selection.NotIn
|
||||
case v1.NodeSelectorOpExists:
|
||||
op = selection.Exists
|
||||
case v1.NodeSelectorOpDoesNotExist:
|
||||
op = selection.DoesNotExist
|
||||
case v1.NodeSelectorOpGt:
|
||||
op = selection.GreaterThan
|
||||
case v1.NodeSelectorOpLt:
|
||||
op = selection.LessThan
|
||||
default:
|
||||
return nil, fmt.Errorf("%q is not a valid node selector operator", nsr.Operator)
|
||||
}
|
||||
return labels.NewRequirement(nsr.Key, op, nsr.Values)
|
||||
}
|
||||
|
||||
// NodeSelectorAsSelector converts the NodeSelector api type into a struct that
|
||||
// implements labels.Selector
|
||||
// Note: This function should be kept in sync with the selector methods in
|
||||
// pkg/labels/selector.go
|
||||
func NodeSelectorAsSelector(ns *v1.NodeSelector) (labels.Selector, error) {
|
||||
if ns == nil {
|
||||
return labels.Nothing(), nil
|
||||
}
|
||||
if len(ns.NodeSelectorTerms) == 0 {
|
||||
return labels.Everything(), nil
|
||||
}
|
||||
var requirements []labels.Requirement
|
||||
|
||||
for _, nsTerm := range ns.NodeSelectorTerms {
|
||||
for _, expr := range nsTerm.MatchExpressions {
|
||||
req, err := nodeSelectorRequirementsAsLabelRequirements(expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requirements = append(requirements, *req)
|
||||
}
|
||||
|
||||
for _, field := range nsTerm.MatchFields {
|
||||
req, err := nodeSelectorRequirementsAsLabelRequirements(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requirements = append(requirements, *req)
|
||||
}
|
||||
}
|
||||
|
||||
selector := labels.NewSelector()
|
||||
selector = selector.Add(requirements...)
|
||||
return selector, nil
|
||||
}
|
||||
|
14
vendor/k8s.io/kubernetes/pkg/apis/core/v1/zz_generated.conversion.go
generated
vendored
14
vendor/k8s.io/kubernetes/pkg/apis/core/v1/zz_generated.conversion.go
generated
vendored
@ -2423,6 +2423,7 @@ func autoConvert_v1_CSIPersistentVolumeSource_To_core_CSIPersistentVolumeSource(
|
||||
out.NodeStageSecretRef = (*core.SecretReference)(unsafe.Pointer(in.NodeStageSecretRef))
|
||||
out.NodePublishSecretRef = (*core.SecretReference)(unsafe.Pointer(in.NodePublishSecretRef))
|
||||
out.ControllerExpandSecretRef = (*core.SecretReference)(unsafe.Pointer(in.ControllerExpandSecretRef))
|
||||
out.NodeExpandSecretRef = (*core.SecretReference)(unsafe.Pointer(in.NodeExpandSecretRef))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2441,6 +2442,7 @@ func autoConvert_core_CSIPersistentVolumeSource_To_v1_CSIPersistentVolumeSource(
|
||||
out.NodeStageSecretRef = (*v1.SecretReference)(unsafe.Pointer(in.NodeStageSecretRef))
|
||||
out.NodePublishSecretRef = (*v1.SecretReference)(unsafe.Pointer(in.NodePublishSecretRef))
|
||||
out.ControllerExpandSecretRef = (*v1.SecretReference)(unsafe.Pointer(in.ControllerExpandSecretRef))
|
||||
out.NodeExpandSecretRef = (*v1.SecretReference)(unsafe.Pointer(in.NodeExpandSecretRef))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -6091,6 +6093,7 @@ func autoConvert_core_PodSecurityContext_To_v1_PodSecurityContext(in *core.PodSe
|
||||
// INFO: in.HostPID opted out of conversion generation
|
||||
// INFO: in.HostIPC opted out of conversion generation
|
||||
// INFO: in.ShareProcessNamespace opted out of conversion generation
|
||||
// INFO: in.HostUsers opted out of conversion generation
|
||||
out.SELinuxOptions = (*v1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions))
|
||||
out.WindowsOptions = (*v1.WindowsSecurityContextOptions)(unsafe.Pointer(in.WindowsOptions))
|
||||
out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser))
|
||||
@ -6184,6 +6187,7 @@ func autoConvert_v1_PodSpec_To_core_PodSpec(in *v1.PodSpec, out *core.PodSpec, s
|
||||
out.TopologySpreadConstraints = *(*[]core.TopologySpreadConstraint)(unsafe.Pointer(&in.TopologySpreadConstraints))
|
||||
out.SetHostnameAsFQDN = (*bool)(unsafe.Pointer(in.SetHostnameAsFQDN))
|
||||
out.OS = (*core.PodOS)(unsafe.Pointer(in.OS))
|
||||
// INFO: in.HostUsers opted out of conversion generation
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -7707,7 +7711,7 @@ func autoConvert_v1_ServiceSpec_To_core_ServiceSpec(in *v1.ServiceSpec, out *cor
|
||||
out.PublishNotReadyAddresses = in.PublishNotReadyAddresses
|
||||
out.SessionAffinityConfig = (*core.SessionAffinityConfig)(unsafe.Pointer(in.SessionAffinityConfig))
|
||||
out.IPFamilies = *(*[]core.IPFamily)(unsafe.Pointer(&in.IPFamilies))
|
||||
out.IPFamilyPolicy = (*core.IPFamilyPolicyType)(unsafe.Pointer(in.IPFamilyPolicy))
|
||||
out.IPFamilyPolicy = (*core.IPFamilyPolicy)(unsafe.Pointer(in.IPFamilyPolicy))
|
||||
out.AllocateLoadBalancerNodePorts = (*bool)(unsafe.Pointer(in.AllocateLoadBalancerNodePorts))
|
||||
out.LoadBalancerClass = (*string)(unsafe.Pointer(in.LoadBalancerClass))
|
||||
out.InternalTrafficPolicy = (*core.ServiceInternalTrafficPolicyType)(unsafe.Pointer(in.InternalTrafficPolicy))
|
||||
@ -7726,7 +7730,7 @@ func autoConvert_core_ServiceSpec_To_v1_ServiceSpec(in *core.ServiceSpec, out *v
|
||||
out.ClusterIP = in.ClusterIP
|
||||
out.ClusterIPs = *(*[]string)(unsafe.Pointer(&in.ClusterIPs))
|
||||
out.IPFamilies = *(*[]v1.IPFamily)(unsafe.Pointer(&in.IPFamilies))
|
||||
out.IPFamilyPolicy = (*v1.IPFamilyPolicyType)(unsafe.Pointer(in.IPFamilyPolicy))
|
||||
out.IPFamilyPolicy = (*v1.IPFamilyPolicy)(unsafe.Pointer(in.IPFamilyPolicy))
|
||||
out.ExternalName = in.ExternalName
|
||||
out.ExternalIPs = *(*[]string)(unsafe.Pointer(&in.ExternalIPs))
|
||||
out.LoadBalancerIP = in.LoadBalancerIP
|
||||
@ -7995,6 +7999,9 @@ func autoConvert_v1_TopologySpreadConstraint_To_core_TopologySpreadConstraint(in
|
||||
out.WhenUnsatisfiable = core.UnsatisfiableConstraintAction(in.WhenUnsatisfiable)
|
||||
out.LabelSelector = (*metav1.LabelSelector)(unsafe.Pointer(in.LabelSelector))
|
||||
out.MinDomains = (*int32)(unsafe.Pointer(in.MinDomains))
|
||||
out.NodeAffinityPolicy = (*core.NodeInclusionPolicy)(unsafe.Pointer(in.NodeAffinityPolicy))
|
||||
out.NodeTaintsPolicy = (*core.NodeInclusionPolicy)(unsafe.Pointer(in.NodeTaintsPolicy))
|
||||
out.MatchLabelKeys = *(*[]string)(unsafe.Pointer(&in.MatchLabelKeys))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -8009,6 +8016,9 @@ func autoConvert_core_TopologySpreadConstraint_To_v1_TopologySpreadConstraint(in
|
||||
out.WhenUnsatisfiable = v1.UnsatisfiableConstraintAction(in.WhenUnsatisfiable)
|
||||
out.LabelSelector = (*metav1.LabelSelector)(unsafe.Pointer(in.LabelSelector))
|
||||
out.MinDomains = (*int32)(unsafe.Pointer(in.MinDomains))
|
||||
out.NodeAffinityPolicy = (*v1.NodeInclusionPolicy)(unsafe.Pointer(in.NodeAffinityPolicy))
|
||||
out.NodeTaintsPolicy = (*v1.NodeInclusionPolicy)(unsafe.Pointer(in.NodeTaintsPolicy))
|
||||
out.MatchLabelKeys = *(*[]string)(unsafe.Pointer(&in.MatchLabelKeys))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
17
vendor/k8s.io/kubernetes/pkg/apis/core/validation/events.go
generated
vendored
17
vendor/k8s.io/kubernetes/pkg/apis/core/validation/events.go
generated
vendored
@ -40,7 +40,7 @@ const (
|
||||
|
||||
func ValidateEventCreate(event *core.Event, requestVersion schema.GroupVersion) field.ErrorList {
|
||||
// Make sure events always pass legacy validation.
|
||||
allErrs := legacyValidateEvent(event)
|
||||
allErrs := legacyValidateEvent(event, requestVersion)
|
||||
if requestVersion == v1.SchemeGroupVersion || requestVersion == eventsv1beta1.SchemeGroupVersion {
|
||||
// No further validation for backwards compatibility.
|
||||
return allErrs
|
||||
@ -73,7 +73,7 @@ func ValidateEventCreate(event *core.Event, requestVersion schema.GroupVersion)
|
||||
|
||||
func ValidateEventUpdate(newEvent, oldEvent *core.Event, requestVersion schema.GroupVersion) field.ErrorList {
|
||||
// Make sure the new event always passes legacy validation.
|
||||
allErrs := legacyValidateEvent(newEvent)
|
||||
allErrs := legacyValidateEvent(newEvent, requestVersion)
|
||||
if requestVersion == v1.SchemeGroupVersion || requestVersion == eventsv1beta1.SchemeGroupVersion {
|
||||
// No further validation for backwards compatibility.
|
||||
return allErrs
|
||||
@ -119,11 +119,16 @@ func validateV1EventSeries(event *core.Event) field.ErrorList {
|
||||
}
|
||||
|
||||
// legacyValidateEvent makes sure that the event makes sense.
|
||||
func legacyValidateEvent(event *core.Event) field.ErrorList {
|
||||
func legacyValidateEvent(event *core.Event, requestVersion schema.GroupVersion) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
// Because go
|
||||
zeroTime := time.Time{}
|
||||
|
||||
reportingControllerFieldName := "reportingController"
|
||||
if requestVersion == v1.SchemeGroupVersion {
|
||||
reportingControllerFieldName = "reportingComponent"
|
||||
}
|
||||
|
||||
// "New" Events need to have EventTime set, so it's validating old object.
|
||||
if event.EventTime.Time == zeroTime {
|
||||
// Make sure event.Namespace and the involvedInvolvedObject.Namespace agree
|
||||
@ -144,11 +149,9 @@ func legacyValidateEvent(event *core.Event) field.ErrorList {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace"))
|
||||
}
|
||||
if len(event.ReportingController) == 0 {
|
||||
allErrs = append(allErrs, field.Required(field.NewPath("reportingController"), ""))
|
||||
}
|
||||
for _, msg := range validation.IsQualifiedName(event.ReportingController) {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("reportingController"), event.ReportingController, msg))
|
||||
allErrs = append(allErrs, field.Required(field.NewPath(reportingControllerFieldName), ""))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateQualifiedName(event.ReportingController, field.NewPath(reportingControllerFieldName))...)
|
||||
if len(event.ReportingInstance) == 0 {
|
||||
allErrs = append(allErrs, field.Required(field.NewPath("reportingInstance"), ""))
|
||||
}
|
||||
|
501
vendor/k8s.io/kubernetes/pkg/apis/core/validation/validation.go
generated
vendored
501
vendor/k8s.io/kubernetes/pkg/apis/core/validation/validation.go
generated
vendored
@ -45,7 +45,6 @@ import (
|
||||
schedulinghelper "k8s.io/component-helpers/scheduling/corev1"
|
||||
apiservice "k8s.io/kubernetes/pkg/api/service"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
|
||||
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||
@ -1530,6 +1529,26 @@ func validateStorageOSPersistentVolumeSource(storageos *core.StorageOSPersistent
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validatePVSecretReference check whether provided SecretReference object is valid in terms of secret name and namespace.
|
||||
|
||||
func validatePVSecretReference(secretRef *core.SecretReference, allowDNSSubDomainSecretName bool, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
if len(secretRef.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
} else if allowDNSSubDomainSecretName {
|
||||
allErrs = append(allErrs, ValidateDNS1123Subdomain(secretRef.Name, fldPath.Child("name"))...)
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(secretRef.Name, fldPath.Child("name"))...)
|
||||
}
|
||||
|
||||
if len(secretRef.Namespace) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(secretRef.Namespace, fldPath.Child("namespace"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func ValidateCSIDriverName(driverName string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@ -1548,7 +1567,7 @@ func ValidateCSIDriverName(driverName string, fldPath *field.Path) field.ErrorLi
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateCSIPersistentVolumeSource(csi *core.CSIPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||
func validateCSIPersistentVolumeSource(csi *core.CSIPersistentVolumeSource, allowDNSSubDomainSecretName bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, ValidateCSIDriverName(csi.Driver, fldPath.Child("driver"))...)
|
||||
@ -1556,46 +1575,18 @@ func validateCSIPersistentVolumeSource(csi *core.CSIPersistentVolumeSource, fldP
|
||||
if len(csi.VolumeHandle) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("volumeHandle"), ""))
|
||||
}
|
||||
|
||||
if csi.ControllerPublishSecretRef != nil {
|
||||
if len(csi.ControllerPublishSecretRef.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("controllerPublishSecretRef", "name"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(csi.ControllerPublishSecretRef.Name, fldPath.Child("name"))...)
|
||||
}
|
||||
if len(csi.ControllerPublishSecretRef.Namespace) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("controllerPublishSecretRef", "namespace"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(csi.ControllerPublishSecretRef.Namespace, fldPath.Child("namespace"))...)
|
||||
}
|
||||
allErrs = append(allErrs, validatePVSecretReference(csi.ControllerPublishSecretRef, allowDNSSubDomainSecretName, fldPath.Child("controllerPublishSecretRef"))...)
|
||||
}
|
||||
|
||||
if csi.ControllerExpandSecretRef != nil {
|
||||
if len(csi.ControllerExpandSecretRef.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("controllerExpandSecretRef", "name"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(csi.ControllerExpandSecretRef.Name, fldPath.Child("name"))...)
|
||||
}
|
||||
if len(csi.ControllerExpandSecretRef.Namespace) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("controllerExpandSecretRef", "namespace"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(csi.ControllerExpandSecretRef.Namespace, fldPath.Child("namespace"))...)
|
||||
}
|
||||
allErrs = append(allErrs, validatePVSecretReference(csi.ControllerExpandSecretRef, allowDNSSubDomainSecretName, fldPath.Child("controllerExpandSecretRef"))...)
|
||||
}
|
||||
|
||||
if csi.NodePublishSecretRef != nil {
|
||||
if len(csi.NodePublishSecretRef.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("nodePublishSecretRef", "name"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(csi.NodePublishSecretRef.Name, fldPath.Child("name"))...)
|
||||
}
|
||||
if len(csi.NodePublishSecretRef.Namespace) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("nodePublishSecretRef", "namespace"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(csi.NodePublishSecretRef.Namespace, fldPath.Child("namespace"))...)
|
||||
}
|
||||
allErrs = append(allErrs, validatePVSecretReference(csi.NodePublishSecretRef, allowDNSSubDomainSecretName, fldPath.Child("nodePublishSecretRef"))...)
|
||||
}
|
||||
if csi.NodeExpandSecretRef != nil {
|
||||
allErrs = append(allErrs, validatePVSecretReference(csi.NodeExpandSecretRef, allowDNSSubDomainSecretName, fldPath.Child("nodeExpandSecretRef"))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@ -1656,6 +1647,8 @@ var allowedPVCTemplateObjectMetaFields = map[string]bool{
|
||||
type PersistentVolumeSpecValidationOptions struct {
|
||||
// Allow spec to contain the "ReadWiteOncePod" access mode
|
||||
AllowReadWriteOncePod bool
|
||||
// Allow the secretRef Name field to be of DNSSubDomain Format
|
||||
AllowDNSSubDomainSecretName bool
|
||||
}
|
||||
|
||||
// ValidatePersistentVolumeName checks that a name is appropriate for a
|
||||
@ -1670,7 +1663,8 @@ var supportedVolumeModes = sets.NewString(string(core.PersistentVolumeBlock), st
|
||||
|
||||
func ValidationOptionsForPersistentVolume(pv, oldPv *core.PersistentVolume) PersistentVolumeSpecValidationOptions {
|
||||
opts := PersistentVolumeSpecValidationOptions{
|
||||
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||
AllowDNSSubDomainSecretName: false,
|
||||
}
|
||||
if oldPv == nil {
|
||||
// If there's no old PV, use the options based solely on feature enablement
|
||||
@ -1680,9 +1674,21 @@ func ValidationOptionsForPersistentVolume(pv, oldPv *core.PersistentVolume) Pers
|
||||
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
|
||||
opts.AllowReadWriteOncePod = true
|
||||
}
|
||||
if oldCSI := oldPv.Spec.CSI; oldCSI != nil {
|
||||
opts.AllowDNSSubDomainSecretName =
|
||||
secretRefRequiresSubdomainSecretName(oldCSI.ControllerExpandSecretRef) ||
|
||||
secretRefRequiresSubdomainSecretName(oldCSI.ControllerPublishSecretRef) ||
|
||||
secretRefRequiresSubdomainSecretName(oldCSI.NodeStageSecretRef) ||
|
||||
secretRefRequiresSubdomainSecretName(oldCSI.NodePublishSecretRef)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func secretRefRequiresSubdomainSecretName(secretRef *core.SecretReference) bool {
|
||||
// ref and name were specified and name didn't fit within label validation
|
||||
return secretRef != nil && len(secretRef.Name) > 0 && len(validation.IsDNS1123Label(secretRef.Name)) > 0
|
||||
}
|
||||
|
||||
func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName string, validateInlinePersistentVolumeSpec bool, fldPath *field.Path, opts PersistentVolumeSpecValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@ -1937,7 +1943,7 @@ func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName stri
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("csi"), "may not specify more than 1 volume type"))
|
||||
} else {
|
||||
numVolumes++
|
||||
allErrs = append(allErrs, validateCSIPersistentVolumeSource(pvSpec.CSI, fldPath.Child("csi"))...)
|
||||
allErrs = append(allErrs, validateCSIPersistentVolumeSource(pvSpec.CSI, opts.AllowDNSSubDomainSecretName, fldPath.Child("csi"))...)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2021,12 +2027,15 @@ type PersistentVolumeClaimSpecValidationOptions struct {
|
||||
AllowReadWriteOncePod bool
|
||||
// Allow users to recover from previously failing expansion operation
|
||||
EnableRecoverFromExpansionFailure bool
|
||||
// Allow assigning StorageClass to unbound PVCs retroactively
|
||||
EnableRetroactiveDefaultStorageClass bool
|
||||
}
|
||||
|
||||
func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions {
|
||||
opts := PersistentVolumeClaimSpecValidationOptions{
|
||||
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||
EnableRecoverFromExpansionFailure: utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure),
|
||||
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||
EnableRecoverFromExpansionFailure: utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure),
|
||||
EnableRetroactiveDefaultStorageClass: utilfeature.DefaultFeatureGate.Enabled(features.RetroactiveDefaultStorageClass),
|
||||
}
|
||||
if oldPvc == nil {
|
||||
// If there's no old PVC, use the options based solely on feature enablement
|
||||
@ -2162,7 +2171,7 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
|
||||
oldPvcClone.Spec.VolumeName = newPvcClone.Spec.VolumeName // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
|
||||
if validateStorageClassUpgrade(oldPvcClone.Annotations, newPvcClone.Annotations,
|
||||
if validateStorageClassUpgradeFromAnnotation(oldPvcClone.Annotations, newPvcClone.Annotations,
|
||||
oldPvcClone.Spec.StorageClassName, newPvcClone.Spec.StorageClassName) {
|
||||
newPvcClone.Spec.StorageClassName = nil
|
||||
metav1.SetMetaDataAnnotation(&newPvcClone.ObjectMeta, core.BetaStorageClassAnnotation, oldPvcClone.Annotations[core.BetaStorageClassAnnotation])
|
||||
@ -2170,6 +2179,13 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
|
||||
// storageclass annotation should be immutable after creation
|
||||
// TODO: remove Beta when no longer needed
|
||||
allErrs = append(allErrs, ValidateImmutableAnnotation(newPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], oldPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], v1.BetaStorageClassAnnotation, field.NewPath("metadata"))...)
|
||||
|
||||
// If update from annotation to attribute failed we can attempt try to validate update from nil value.
|
||||
if validateStorageClassUpgradeFromNil(oldPvc.Annotations, oldPvc.Spec.StorageClassName, newPvc.Spec.StorageClassName, opts) {
|
||||
newPvcClone.Spec.StorageClassName = oldPvcClone.Spec.StorageClassName // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
// TODO: add a specific error with a hint that storage class name can not be changed
|
||||
// (instead of letting spec comparison below return generic field forbidden error)
|
||||
}
|
||||
|
||||
// lets make sure storage values are same.
|
||||
@ -2210,7 +2226,7 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
|
||||
// 2. The old pvc's StorageClassName is not set
|
||||
// 3. The new pvc's StorageClassName is set and equal to the old value in annotation
|
||||
// 4. If the new pvc's StorageClassAnnotation is set,it must be equal to the old pv/pvc's StorageClassAnnotation
|
||||
func validateStorageClassUpgrade(oldAnnotations, newAnnotations map[string]string, oldScName, newScName *string) bool {
|
||||
func validateStorageClassUpgradeFromAnnotation(oldAnnotations, newAnnotations map[string]string, oldScName, newScName *string) bool {
|
||||
oldSc, oldAnnotationExist := oldAnnotations[core.BetaStorageClassAnnotation]
|
||||
newScInAnnotation, newAnnotationExist := newAnnotations[core.BetaStorageClassAnnotation]
|
||||
return oldAnnotationExist /* condition 1 */ &&
|
||||
@ -2219,6 +2235,20 @@ func validateStorageClassUpgrade(oldAnnotations, newAnnotations map[string]strin
|
||||
(!newAnnotationExist || newScInAnnotation == oldSc) /* condition 4 */
|
||||
}
|
||||
|
||||
// Provide an upgrade path from PVC with nil storage class. We allow update of
|
||||
// StorageClassName only if following four conditions are met at the same time:
|
||||
// 1. RetroactiveDefaultStorageClass FeatureGate is enabled
|
||||
// 2. The new pvc's StorageClassName is not nil
|
||||
// 3. The old pvc's StorageClassName is nil
|
||||
// 4. The old pvc either does not have beta annotation set, or the beta annotation matches new pvc's StorageClassName
|
||||
func validateStorageClassUpgradeFromNil(oldAnnotations map[string]string, oldScName, newScName *string, opts PersistentVolumeClaimSpecValidationOptions) bool {
|
||||
oldAnnotation, oldAnnotationExist := oldAnnotations[core.BetaStorageClassAnnotation]
|
||||
return opts.EnableRetroactiveDefaultStorageClass /* condition 1 */ &&
|
||||
newScName != nil /* condition 2 */ &&
|
||||
oldScName == nil /* condition 3 */ &&
|
||||
(!oldAnnotationExist || *newScName == oldAnnotation) /* condition 4 */
|
||||
}
|
||||
|
||||
var resizeStatusSet = sets.NewString(string(core.PersistentVolumeClaimNoExpansionInProgress),
|
||||
string(core.PersistentVolumeClaimControllerExpansionInProgress),
|
||||
string(core.PersistentVolumeClaimControllerExpansionFailed),
|
||||
@ -2405,13 +2435,9 @@ func validateObjectFieldSelector(fs *core.ObjectFieldSelector, expressions *sets
|
||||
if path, subscript, ok := fieldpath.SplitMaybeSubscriptedPath(internalFieldPath); ok {
|
||||
switch path {
|
||||
case "metadata.annotations":
|
||||
for _, msg := range validation.IsQualifiedName(strings.ToLower(subscript)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, subscript, msg))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateQualifiedName(strings.ToLower(subscript), fldPath)...)
|
||||
case "metadata.labels":
|
||||
for _, msg := range validation.IsQualifiedName(subscript) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, subscript, msg))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateQualifiedName(subscript, fldPath)...)
|
||||
default:
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, path, "does not support subscript"))
|
||||
}
|
||||
@ -2905,6 +2931,8 @@ func validatePullPolicy(policy core.PullPolicy, fldPath *field.Path) field.Error
|
||||
return allErrors
|
||||
}
|
||||
|
||||
// validateEphemeralContainers is called by pod spec and template validation to validate the list of ephemeral containers.
|
||||
// Note that this is called for pod template even though ephemeral containers aren't allowed in pod templates.
|
||||
func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer, containers, initContainers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@ -2912,44 +2940,40 @@ func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer,
|
||||
return allErrs
|
||||
}
|
||||
|
||||
allNames := sets.String{}
|
||||
otherNames, allNames := sets.String{}, sets.String{}
|
||||
for _, c := range containers {
|
||||
otherNames.Insert(c.Name)
|
||||
allNames.Insert(c.Name)
|
||||
}
|
||||
for _, c := range initContainers {
|
||||
otherNames.Insert(c.Name)
|
||||
allNames.Insert(c.Name)
|
||||
}
|
||||
|
||||
for i, ec := range ephemeralContainers {
|
||||
idxPath := fldPath.Index(i)
|
||||
|
||||
if ec.TargetContainerName != "" && !allNames.Has(ec.TargetContainerName) {
|
||||
allErrs = append(allErrs, field.NotFound(idxPath.Child("targetContainerName"), ec.TargetContainerName))
|
||||
}
|
||||
|
||||
if ec.Name == "" {
|
||||
allErrs = append(allErrs, field.Required(idxPath, "ephemeralContainer requires a name"))
|
||||
continue
|
||||
}
|
||||
|
||||
// Using validateContainers() here isn't ideal because it adds an index to the error message that
|
||||
// doesn't really exist for EphemeralContainers (i.e. ephemeralContainers[0].spec[0].name instead
|
||||
// of ephemeralContainers[0].spec.name)
|
||||
// TODO(verb): factor a validateContainer() out of validateContainers() to be used here
|
||||
c := core.Container(ec.EphemeralContainerCommon)
|
||||
allErrs = append(allErrs, validateContainers([]core.Container{c}, false, volumes, idxPath, opts)...)
|
||||
// EphemeralContainers don't require the backwards-compatibility distinction between pod/podTemplate validation
|
||||
allErrs = append(allErrs, validateContainersOnlyForPod([]core.Container{c}, idxPath)...)
|
||||
c := (*core.Container)(&ec.EphemeralContainerCommon)
|
||||
allErrs = append(allErrs, validateContainerCommon(c, volumes, idxPath, opts)...)
|
||||
// Ephemeral containers don't need looser constraints for pod templates, so it's convenient to apply both validations
|
||||
// here where we've already converted EphemeralContainerCommon to Container.
|
||||
allErrs = append(allErrs, validateContainerOnlyForPod(c, idxPath)...)
|
||||
|
||||
// Ephemeral containers must have a name unique across all container types.
|
||||
if allNames.Has(ec.Name) {
|
||||
allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ec.Name))
|
||||
} else {
|
||||
allNames.Insert(ec.Name)
|
||||
}
|
||||
|
||||
// Ephemeral Containers should not be relied upon for fundamental pod services, so fields such as
|
||||
// The target container name must exist and be non-ephemeral.
|
||||
if ec.TargetContainerName != "" && !otherNames.Has(ec.TargetContainerName) {
|
||||
allErrs = append(allErrs, field.NotFound(idxPath.Child("targetContainerName"), ec.TargetContainerName))
|
||||
}
|
||||
|
||||
// Ephemeral containers should not be relied upon for fundamental pod services, so fields such as
|
||||
// Lifecycle, probes, resources and ports should be disallowed. This is implemented as a list
|
||||
// of allowed fields so that new fields will be given consideration prior to inclusion in Ephemeral Containers.
|
||||
// of allowed fields so that new fields will be given consideration prior to inclusion in ephemeral containers.
|
||||
allErrs = append(allErrs, validateFieldAllowList(ec.EphemeralContainerCommon, allowedEphemeralContainerFields, "cannot be set for an Ephemeral Container", idxPath)...)
|
||||
|
||||
// VolumeMount subpaths have the potential to leak resources since they're implemented with bind mounts
|
||||
@ -2991,41 +3015,142 @@ func validateFieldAllowList(value interface{}, allowedFields map[string]bool, er
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateInitContainers(containers []core.Container, otherContainers []core.Container, deviceVolumes map[string]core.VolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
// validateInitContainers is called by pod spec and template validation to validate the list of init containers
|
||||
func validateInitContainers(containers []core.Container, regularContainers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
if len(containers) > 0 {
|
||||
allErrs = append(allErrs, validateContainers(containers, true, deviceVolumes, fldPath, opts)...)
|
||||
}
|
||||
|
||||
allNames := sets.String{}
|
||||
for _, ctr := range otherContainers {
|
||||
for _, ctr := range regularContainers {
|
||||
allNames.Insert(ctr.Name)
|
||||
}
|
||||
for i, ctr := range containers {
|
||||
idxPath := fldPath.Index(i)
|
||||
|
||||
// Apply the validation common to all container types
|
||||
allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, idxPath, opts)...)
|
||||
|
||||
// Names must be unique within regular and init containers. Collisions with ephemeral containers
|
||||
// will be detected by validateEphemeralContainers().
|
||||
if allNames.Has(ctr.Name) {
|
||||
allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ctr.Name))
|
||||
}
|
||||
if len(ctr.Name) > 0 {
|
||||
} else if len(ctr.Name) > 0 {
|
||||
allNames.Insert(ctr.Name)
|
||||
}
|
||||
|
||||
// Check for port conflicts in init containers individually since init containers run one-by-one.
|
||||
allErrs = append(allErrs, checkHostPortConflicts([]core.Container{ctr}, fldPath)...)
|
||||
|
||||
// These fields are disallowed for init containers.
|
||||
if ctr.Lifecycle != nil {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("lifecycle"), ctr.Lifecycle, "must not be set for init containers"))
|
||||
allErrs = append(allErrs, field.Forbidden(idxPath.Child("lifecycle"), "may not be set for init containers"))
|
||||
}
|
||||
if ctr.LivenessProbe != nil {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("livenessProbe"), ctr.LivenessProbe, "must not be set for init containers"))
|
||||
allErrs = append(allErrs, field.Forbidden(idxPath.Child("livenessProbe"), "may not be set for init containers"))
|
||||
}
|
||||
if ctr.ReadinessProbe != nil {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("readinessProbe"), ctr.ReadinessProbe, "must not be set for init containers"))
|
||||
allErrs = append(allErrs, field.Forbidden(idxPath.Child("readinessProbe"), "may not be set for init containers"))
|
||||
}
|
||||
if ctr.StartupProbe != nil {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("startupProbe"), ctr.StartupProbe, "must not be set for init containers"))
|
||||
allErrs = append(allErrs, field.Forbidden(idxPath.Child("startupProbe"), "may not be set for init containers"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateContainers(containers []core.Container, isInitContainers bool, volumes map[string]core.VolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
// validateContainerCommon applies validation common to all container types. It's called by regular, init, and ephemeral
|
||||
// container list validation to require a properly formatted name, image, etc.
|
||||
func validateContainerCommon(ctr *core.Container, volumes map[string]core.VolumeSource, path *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
namePath := path.Child("name")
|
||||
if len(ctr.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(namePath, ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(ctr.Name, namePath)...)
|
||||
}
|
||||
|
||||
// TODO: do not validate leading and trailing whitespace to preserve backward compatibility.
|
||||
// for example: https://github.com/openshift/origin/issues/14659 image = " " is special token in pod template
|
||||
// others may have done similar
|
||||
if len(ctr.Image) == 0 {
|
||||
allErrs = append(allErrs, field.Required(path.Child("image"), ""))
|
||||
}
|
||||
|
||||
switch ctr.TerminationMessagePolicy {
|
||||
case core.TerminationMessageReadFile, core.TerminationMessageFallbackToLogsOnError:
|
||||
case "":
|
||||
allErrs = append(allErrs, field.Required(path.Child("terminationMessagePolicy"), ""))
|
||||
default:
|
||||
supported := []string{
|
||||
string(core.TerminationMessageReadFile),
|
||||
string(core.TerminationMessageFallbackToLogsOnError),
|
||||
}
|
||||
allErrs = append(allErrs, field.NotSupported(path.Child("terminationMessagePolicy"), ctr.TerminationMessagePolicy, supported))
|
||||
}
|
||||
|
||||
volMounts := GetVolumeMountMap(ctr.VolumeMounts)
|
||||
volDevices := GetVolumeDeviceMap(ctr.VolumeDevices)
|
||||
allErrs = append(allErrs, validateContainerPorts(ctr.Ports, path.Child("ports"))...)
|
||||
allErrs = append(allErrs, ValidateEnv(ctr.Env, path.Child("env"), opts)...)
|
||||
allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, path.Child("envFrom"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, ctr, path.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, path.Child("volumeDevices"))...)
|
||||
allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, path.Child("imagePullPolicy"))...)
|
||||
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, path.Child("resources"), opts)...)
|
||||
allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, path.Child("securityContext"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateHostUsers(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
// Only make the following checks if hostUsers is false (otherwise, the container uses the
|
||||
// same userns as the host, and so there isn't anything to check).
|
||||
if spec.SecurityContext == nil || spec.SecurityContext.HostUsers == nil || *spec.SecurityContext.HostUsers == true {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// For now only these volumes are supported:
|
||||
// - configmap
|
||||
// - secret
|
||||
// - downwardAPI
|
||||
// - emptyDir
|
||||
// - projected
|
||||
// So reject anything else.
|
||||
for i, vol := range spec.Volumes {
|
||||
switch {
|
||||
case vol.EmptyDir != nil:
|
||||
case vol.Secret != nil:
|
||||
case vol.DownwardAPI != nil:
|
||||
case vol.ConfigMap != nil:
|
||||
case vol.Projected != nil:
|
||||
default:
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumes").Index(i), "volume type not supported when `pod.Spec.HostUsers` is false"))
|
||||
}
|
||||
}
|
||||
|
||||
// We decided to restrict the usage of userns with other host namespaces:
|
||||
// https://github.com/kubernetes/kubernetes/pull/111090#discussion_r935994282
|
||||
// The tl;dr is: you can easily run into permission issues that seem unexpected, we don't
|
||||
// know of any good use case and we can always enable them later.
|
||||
|
||||
// Note we already validated above spec.SecurityContext is not nil.
|
||||
if spec.SecurityContext.HostNetwork {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostNetwork"), "when `pod.Spec.HostUsers` is false"))
|
||||
}
|
||||
if spec.SecurityContext.HostPID {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("HostPID"), "when `pod.Spec.HostUsers` is false"))
|
||||
}
|
||||
if spec.SecurityContext.HostIPC {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("HostIPC"), "when `pod.Spec.HostUsers` is false"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateContainers is called by pod spec and template validation to validate the list of regular containers.
|
||||
func validateContainers(containers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if len(containers) == 0 {
|
||||
@ -3034,74 +3159,41 @@ func validateContainers(containers []core.Container, isInitContainers bool, volu
|
||||
|
||||
allNames := sets.String{}
|
||||
for i, ctr := range containers {
|
||||
idxPath := fldPath.Index(i)
|
||||
namePath := idxPath.Child("name")
|
||||
volMounts := GetVolumeMountMap(ctr.VolumeMounts)
|
||||
volDevices := GetVolumeDeviceMap(ctr.VolumeDevices)
|
||||
path := fldPath.Index(i)
|
||||
|
||||
if len(ctr.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(namePath, ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(ctr.Name, namePath)...)
|
||||
}
|
||||
// Apply validation common to all containers
|
||||
allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, path, opts)...)
|
||||
|
||||
// Container names must be unique within the list of regular containers.
|
||||
// Collisions with init or ephemeral container names will be detected by the init or ephemeral
|
||||
// container validation to prevent duplicate error messages.
|
||||
if allNames.Has(ctr.Name) {
|
||||
allErrs = append(allErrs, field.Duplicate(namePath, ctr.Name))
|
||||
allErrs = append(allErrs, field.Duplicate(path.Child("name"), ctr.Name))
|
||||
} else {
|
||||
allNames.Insert(ctr.Name)
|
||||
}
|
||||
// TODO: do not validate leading and trailing whitespace to preserve backward compatibility.
|
||||
// for example: https://github.com/openshift/origin/issues/14659 image = " " is special token in pod template
|
||||
// others may have done similar
|
||||
if len(ctr.Image) == 0 {
|
||||
allErrs = append(allErrs, field.Required(idxPath.Child("image"), ""))
|
||||
}
|
||||
|
||||
// These fields are only allowed for regular containers, so only check supported values here.
|
||||
// Init and ephemeral container validation will return field.Forbidden() for these paths.
|
||||
if ctr.Lifecycle != nil {
|
||||
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, idxPath.Child("lifecycle"))...)
|
||||
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, path.Child("lifecycle"))...)
|
||||
}
|
||||
allErrs = append(allErrs, validateProbe(ctr.LivenessProbe, idxPath.Child("livenessProbe"))...)
|
||||
// Readiness-specific validation
|
||||
if ctr.ReadinessProbe != nil && ctr.ReadinessProbe.TerminationGracePeriodSeconds != nil {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("readinessProbe", "terminationGracePeriodSeconds"), ctr.ReadinessProbe.TerminationGracePeriodSeconds, "must not be set for readinessProbes"))
|
||||
}
|
||||
allErrs = append(allErrs, validateProbe(ctr.StartupProbe, idxPath.Child("startupProbe"))...)
|
||||
// Liveness-specific validation
|
||||
allErrs = append(allErrs, validateProbe(ctr.LivenessProbe, path.Child("livenessProbe"))...)
|
||||
if ctr.LivenessProbe != nil && ctr.LivenessProbe.SuccessThreshold != 1 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("livenessProbe", "successThreshold"), ctr.LivenessProbe.SuccessThreshold, "must be 1"))
|
||||
allErrs = append(allErrs, field.Invalid(path.Child("livenessProbe", "successThreshold"), ctr.LivenessProbe.SuccessThreshold, "must be 1"))
|
||||
}
|
||||
allErrs = append(allErrs, validateProbe(ctr.StartupProbe, idxPath.Child("startupProbe"))...)
|
||||
// Startup-specific validation
|
||||
allErrs = append(allErrs, validateProbe(ctr.ReadinessProbe, path.Child("readinessProbe"))...)
|
||||
if ctr.ReadinessProbe != nil && ctr.ReadinessProbe.TerminationGracePeriodSeconds != nil {
|
||||
allErrs = append(allErrs, field.Invalid(path.Child("readinessProbe", "terminationGracePeriodSeconds"), ctr.ReadinessProbe.TerminationGracePeriodSeconds, "must not be set for readinessProbes"))
|
||||
}
|
||||
allErrs = append(allErrs, validateProbe(ctr.StartupProbe, path.Child("startupProbe"))...)
|
||||
if ctr.StartupProbe != nil && ctr.StartupProbe.SuccessThreshold != 1 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("startupProbe", "successThreshold"), ctr.StartupProbe.SuccessThreshold, "must be 1"))
|
||||
allErrs = append(allErrs, field.Invalid(path.Child("startupProbe", "successThreshold"), ctr.StartupProbe.SuccessThreshold, "must be 1"))
|
||||
}
|
||||
|
||||
switch ctr.TerminationMessagePolicy {
|
||||
case core.TerminationMessageReadFile, core.TerminationMessageFallbackToLogsOnError:
|
||||
case "":
|
||||
allErrs = append(allErrs, field.Required(idxPath.Child("terminationMessagePolicy"), "must be 'File' or 'FallbackToLogsOnError'"))
|
||||
default:
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("terminationMessagePolicy"), ctr.TerminationMessagePolicy, "must be 'File' or 'FallbackToLogsOnError'"))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateProbe(ctr.ReadinessProbe, idxPath.Child("readinessProbe"))...)
|
||||
allErrs = append(allErrs, validateContainerPorts(ctr.Ports, idxPath.Child("ports"))...)
|
||||
allErrs = append(allErrs, ValidateEnv(ctr.Env, idxPath.Child("env"), opts)...)
|
||||
allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, idxPath.Child("envFrom"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, &ctr, idxPath.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, idxPath.Child("volumeDevices"))...)
|
||||
allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, idxPath.Child("imagePullPolicy"))...)
|
||||
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"), opts)...)
|
||||
allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, idxPath.Child("securityContext"))...)
|
||||
}
|
||||
|
||||
if isInitContainers {
|
||||
// check initContainers one by one since they are running in sequential order.
|
||||
for _, initContainer := range containers {
|
||||
allErrs = append(allErrs, checkHostPortConflicts([]core.Container{initContainer}, fldPath)...)
|
||||
}
|
||||
} else {
|
||||
// Check for colliding ports across all containers.
|
||||
allErrs = append(allErrs, checkHostPortConflicts(containers, fldPath)...)
|
||||
}
|
||||
// Port conflicts are checked across all containers
|
||||
allErrs = append(allErrs, checkHostPortConflicts(containers, fldPath)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
@ -3175,9 +3267,7 @@ const (
|
||||
func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for i, value := range readinessGates {
|
||||
for _, msg := range validation.IsQualifiedName(string(value.ConditionType)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("conditionType"), string(value.ConditionType), msg))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateQualifiedName(string(value.ConditionType), fldPath.Index(i).Child("conditionType"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
@ -3389,14 +3479,22 @@ func ValidateTolerations(tolerations []core.Toleration, fldPath *field.Path) fie
|
||||
}
|
||||
|
||||
// validateContainersOnlyForPod does additional validation for containers on a pod versus a pod template
|
||||
// it only does additive validation of fields not covered in validateContainers
|
||||
// it only does additive validation of fields not covered in validateContainers and is not called for
|
||||
// ephemeral containers which require a conversion to core.Container.
|
||||
func validateContainersOnlyForPod(containers []core.Container, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for i, ctr := range containers {
|
||||
idxPath := fldPath.Index(i)
|
||||
if len(ctr.Image) != len(strings.TrimSpace(ctr.Image)) {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("image"), ctr.Image, "must not have leading or trailing whitespace"))
|
||||
}
|
||||
allErrs = append(allErrs, validateContainerOnlyForPod(&ctr, fldPath.Index(i))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateContainerOnlyForPod does pod-only (i.e. not pod template) validation for a single container.
|
||||
// This is called by validateContainersOnlyForPod and validateEphemeralContainers directly.
|
||||
func validateContainerOnlyForPod(ctr *core.Container, path *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(ctr.Image) != len(strings.TrimSpace(ctr.Image)) {
|
||||
allErrs = append(allErrs, field.Invalid(path.Child("image"), ctr.Image, "must not have leading or trailing whitespace"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
@ -3413,10 +3511,6 @@ type PodValidationOptions struct {
|
||||
AllowWindowsHostProcessField bool
|
||||
// Allow more DNSSearchPaths and longer DNSSearchListChars
|
||||
AllowExpandedDNSConfig bool
|
||||
// Allow OSField to be set in the pod spec
|
||||
AllowOSField bool
|
||||
// Allow sysctl name to contain a slash
|
||||
AllowSysctlRegexContainSlash bool
|
||||
}
|
||||
|
||||
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,
|
||||
@ -3447,6 +3541,7 @@ 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"))...)
|
||||
// validateContainersOnlyForPod() is checked for ephemeral containers by validateEphemeralContainers()
|
||||
|
||||
return allErrs
|
||||
}
|
||||
@ -3507,7 +3602,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
|
||||
|
||||
vols, vErrs := ValidateVolumes(spec.Volumes, podMeta, fldPath.Child("volumes"), opts)
|
||||
allErrs = append(allErrs, vErrs...)
|
||||
allErrs = append(allErrs, validateContainers(spec.Containers, false, vols, fldPath.Child("containers"), opts)...)
|
||||
allErrs = append(allErrs, validateContainers(spec.Containers, vols, fldPath.Child("containers"), opts)...)
|
||||
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"), opts)...)
|
||||
allErrs = append(allErrs, validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, fldPath.Child("ephemeralContainers"), opts)...)
|
||||
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
|
||||
@ -3520,6 +3615,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
|
||||
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)...)
|
||||
allErrs = append(allErrs, validateHostUsers(spec, fldPath)...)
|
||||
if len(spec.ServiceAccountName) > 0 {
|
||||
for _, msg := range ValidateServiceAccountName(spec.ServiceAccountName, false) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceAccountName"), spec.ServiceAccountName, msg))
|
||||
@ -3612,6 +3708,9 @@ func validateWindows(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||
if securityContext.SELinuxOptions != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("seLinuxOptions"), "cannot be set for a windows pod"))
|
||||
}
|
||||
if securityContext.HostUsers != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostUsers"), "cannot be set for a windows pod"))
|
||||
}
|
||||
if securityContext.HostPID {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPID"), "cannot be set for a windows pod"))
|
||||
}
|
||||
@ -4075,9 +4174,6 @@ const (
|
||||
// a sysctl segment regex, concatenated with dots to form a sysctl name
|
||||
SysctlSegmentFmt string = "[a-z0-9]([-_a-z0-9]*[a-z0-9])?"
|
||||
|
||||
// a sysctl name regex
|
||||
SysctlFmt string = "(" + SysctlSegmentFmt + "\\.)*" + SysctlSegmentFmt
|
||||
|
||||
// a sysctl name regex with slash allowed
|
||||
SysctlContainSlashFmt string = "(" + SysctlSegmentFmt + "[\\./])*" + SysctlSegmentFmt
|
||||
|
||||
@ -4085,41 +4181,29 @@ const (
|
||||
SysctlMaxLength int = 253
|
||||
)
|
||||
|
||||
var sysctlRegexp = regexp.MustCompile("^" + SysctlFmt + "$")
|
||||
|
||||
var sysctlContainSlashRegexp = regexp.MustCompile("^" + SysctlContainSlashFmt + "$")
|
||||
|
||||
// IsValidSysctlName checks that the given string is a valid sysctl name,
|
||||
// i.e. matches SysctlFmt (or SysctlContainSlashFmt if canContainSlash is true).
|
||||
// i.e. matches SysctlContainSlashFmt.
|
||||
// More info:
|
||||
// https://man7.org/linux/man-pages/man8/sysctl.8.html
|
||||
// https://man7.org/linux/man-pages/man5/sysctl.d.5.html
|
||||
func IsValidSysctlName(name string, canContainSlash bool) bool {
|
||||
//
|
||||
// https://man7.org/linux/man-pages/man8/sysctl.8.html
|
||||
// https://man7.org/linux/man-pages/man5/sysctl.d.5.html
|
||||
func IsValidSysctlName(name string) bool {
|
||||
if len(name) > SysctlMaxLength {
|
||||
return false
|
||||
}
|
||||
if canContainSlash {
|
||||
return sysctlContainSlashRegexp.MatchString(name)
|
||||
}
|
||||
return sysctlRegexp.MatchString(name)
|
||||
return sysctlContainSlashRegexp.MatchString(name)
|
||||
}
|
||||
|
||||
func getSysctlFmt(canContainSlash bool) string {
|
||||
if canContainSlash {
|
||||
// use relaxed validation everywhere in 1.24
|
||||
return SysctlContainSlashFmt
|
||||
}
|
||||
// Will be removed in 1.24
|
||||
return SysctlFmt
|
||||
}
|
||||
func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path, allowSysctlRegexContainSlash bool) field.ErrorList {
|
||||
func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
names := make(map[string]struct{})
|
||||
for i, s := range sysctls {
|
||||
if len(s.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), ""))
|
||||
} else if !IsValidSysctlName(s.Name, allowSysctlRegexContainSlash) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("name"), s.Name, fmt.Sprintf("must have at most %d characters and match regex %s", SysctlMaxLength, getSysctlFmt(allowSysctlRegexContainSlash))))
|
||||
} else if !IsValidSysctlName(s.Name) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("name"), s.Name, fmt.Sprintf("must have at most %d characters and match regex %s", SysctlMaxLength, sysctlContainSlashRegexp)))
|
||||
} else if _, ok := names[s.Name]; ok {
|
||||
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("name"), s.Name))
|
||||
}
|
||||
@ -4159,7 +4243,7 @@ func ValidatePodSecurityContext(securityContext *core.PodSecurityContext, spec *
|
||||
}
|
||||
|
||||
if len(securityContext.Sysctls) != 0 {
|
||||
allErrs = append(allErrs, validateSysctls(securityContext.Sysctls, fldPath.Child("sysctls"), opts.AllowSysctlRegexContainSlash)...)
|
||||
allErrs = append(allErrs, validateSysctls(securityContext.Sysctls, fldPath.Child("sysctls"))...)
|
||||
}
|
||||
|
||||
if securityContext.FSGroupChangePolicy != nil {
|
||||
@ -4208,7 +4292,7 @@ func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateSeccompAnnotationsAndFields iterates through all containers and ensure that when both seccompProfile and seccomp annotations exist they match.
|
||||
// validateSeccompAnnotationsAndFields iterates through all containers and ensure that when both seccompProfile and seccomp annotations exist they match.
|
||||
func validateSeccompAnnotationsAndFields(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@ -4435,9 +4519,7 @@ func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path)
|
||||
if systemConditions.Has(string(condition.Type)) {
|
||||
continue
|
||||
}
|
||||
for _, msg := range validation.IsQualifiedName(string(condition.Type)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("Type"), string(condition.Type), msg))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateQualifiedName(string(condition.Type), fldPath.Index(i).Child("Type"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
@ -4719,9 +4801,7 @@ func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService bo
|
||||
allErrs = append(allErrs, ValidatePortNumOrName(sp.TargetPort, fldPath.Child("targetPort"))...)
|
||||
|
||||
if sp.AppProtocol != nil {
|
||||
for _, msg := range validation.IsQualifiedName(*sp.AppProtocol) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("appProtocol"), sp.AppProtocol, msg))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateQualifiedName(*sp.AppProtocol, fldPath.Child("appProtocol"))...)
|
||||
}
|
||||
|
||||
// in the v1 API, targetPorts on headless services were tolerated.
|
||||
@ -4736,7 +4816,7 @@ func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService bo
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func needsExternalTrafficPolicy(svc *api.Service) bool {
|
||||
func needsExternalTrafficPolicy(svc *core.Service) bool {
|
||||
return svc.Spec.Type == core.ServiceTypeLoadBalancer || svc.Spec.Type == core.ServiceTypeNodePort
|
||||
}
|
||||
|
||||
@ -4781,7 +4861,7 @@ func validateServiceExternalTrafficPolicy(service *core.Service) field.ErrorList
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateServiceExternalTrafficFieldsUpdate(before, after *api.Service) field.ErrorList {
|
||||
func validateServiceExternalTrafficFieldsUpdate(before, after *core.Service) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if apiservice.NeedsHealthCheck(before) && apiservice.NeedsHealthCheck(after) {
|
||||
@ -6120,9 +6200,7 @@ func validateEndpointPort(port *core.EndpointPort, requireName bool, fldPath *fi
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), port.Protocol, supportedPortProtocols.List()))
|
||||
}
|
||||
if port.AppProtocol != nil {
|
||||
for _, msg := range validation.IsQualifiedName(*port.AppProtocol) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("appProtocol"), port.AppProtocol, msg))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateQualifiedName(*port.AppProtocol, fldPath.Child("appProtocol"))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
@ -6386,9 +6464,6 @@ func validateOS(podSpec *core.PodSpec, fldPath *field.Path, opts PodValidationOp
|
||||
if os == nil {
|
||||
return allErrs
|
||||
}
|
||||
if !opts.AllowOSField {
|
||||
return append(allErrs, field.Forbidden(fldPath, "cannot be set when IdentifyPodOS feature is not enabled"))
|
||||
}
|
||||
if len(os.Name) == 0 {
|
||||
return append(allErrs, field.Required(fldPath.Child("name"), "cannot be empty"))
|
||||
}
|
||||
@ -6513,6 +6588,13 @@ func validateTopologySpreadConstraints(constraints []core.TopologySpreadConstrai
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
allErrs = append(allErrs, validateMinDomains(subFldPath.Child("minDomains"), constraint.MinDomains, constraint.WhenUnsatisfiable)...)
|
||||
if err := validateNodeInclusionPolicy(subFldPath.Child("nodeAffinityPolicy"), constraint.NodeAffinityPolicy); err != nil {
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
if err := validateNodeInclusionPolicy(subFldPath.Child("nodeTaintsPolicy"), constraint.NodeTaintsPolicy); err != nil {
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
allErrs = append(allErrs, validateMatchLabelKeys(subFldPath.Child("matchLabelKeys"), constraint.MatchLabelKeys, constraint.LabelSelector)...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
@ -6570,6 +6652,49 @@ func ValidateSpreadConstraintNotRepeat(fldPath *field.Path, constraint core.Topo
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
supportedPodTopologySpreadNodePolicies = sets.NewString(string(core.NodeInclusionPolicyIgnore), string(core.NodeInclusionPolicyHonor))
|
||||
)
|
||||
|
||||
// validateNodeAffinityPolicy tests that the argument is a valid NodeInclusionPolicy.
|
||||
func validateNodeInclusionPolicy(fldPath *field.Path, policy *core.NodeInclusionPolicy) *field.Error {
|
||||
if policy == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !supportedPodTopologySpreadNodePolicies.Has(string(*policy)) {
|
||||
return field.NotSupported(fldPath, policy, supportedPodTopologySpreadNodePolicies.List())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMatchLabelKeys tests that the elements are a valid label name and are not already included in labelSelector.
|
||||
func validateMatchLabelKeys(fldPath *field.Path, matchLabelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList {
|
||||
if len(matchLabelKeys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
labelSelectorKeys := sets.String{}
|
||||
if labelSelector != nil {
|
||||
for key := range labelSelector.MatchLabels {
|
||||
labelSelectorKeys.Insert(key)
|
||||
}
|
||||
for _, matchExpression := range labelSelector.MatchExpressions {
|
||||
labelSelectorKeys.Insert(matchExpression.Key)
|
||||
}
|
||||
}
|
||||
|
||||
allErrs := field.ErrorList{}
|
||||
for i, key := range matchLabelKeys {
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(key, fldPath.Index(i))...)
|
||||
if labelSelectorKeys.Has(key) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), key, "exists in both matchLabelKeys and labelSelector"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateServiceClusterIPsRelatedFields validates .spec.ClusterIPs,,
|
||||
// .spec.IPFamilies, .spec.ipFamilyPolicy. This is exported because it is used
|
||||
// during IP init and allocation.
|
||||
|
27
vendor/k8s.io/kubernetes/pkg/apis/core/zz_generated.deepcopy.go
generated
vendored
27
vendor/k8s.io/kubernetes/pkg/apis/core/zz_generated.deepcopy.go
generated
vendored
@ -243,6 +243,11 @@ func (in *CSIPersistentVolumeSource) DeepCopyInto(out *CSIPersistentVolumeSource
|
||||
*out = new(SecretReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.NodeExpandSecretRef != nil {
|
||||
in, out := &in.NodeExpandSecretRef, &out.NodeExpandSecretRef
|
||||
*out = new(SecretReference)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -3731,6 +3736,11 @@ func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) {
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.HostUsers != nil {
|
||||
in, out := &in.HostUsers, &out.HostUsers
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.SELinuxOptions != nil {
|
||||
in, out := &in.SELinuxOptions, &out.SELinuxOptions
|
||||
*out = new(SELinuxOptions)
|
||||
@ -5370,7 +5380,7 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
|
||||
}
|
||||
if in.IPFamilyPolicy != nil {
|
||||
in, out := &in.IPFamilyPolicy, &out.IPFamilyPolicy
|
||||
*out = new(IPFamilyPolicyType)
|
||||
*out = new(IPFamilyPolicy)
|
||||
**out = **in
|
||||
}
|
||||
if in.ExternalIPs != nil {
|
||||
@ -5634,6 +5644,21 @@ func (in *TopologySpreadConstraint) DeepCopyInto(out *TopologySpreadConstraint)
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.NodeAffinityPolicy != nil {
|
||||
in, out := &in.NodeAffinityPolicy, &out.NodeAffinityPolicy
|
||||
*out = new(NodeInclusionPolicy)
|
||||
**out = **in
|
||||
}
|
||||
if in.NodeTaintsPolicy != nil {
|
||||
in, out := &in.NodeTaintsPolicy, &out.NodeTaintsPolicy
|
||||
*out = new(NodeInclusionPolicy)
|
||||
**out = **in
|
||||
}
|
||||
if in.MatchLabelKeys != nil {
|
||||
in, out := &in.MatchLabelKeys, &out.MatchLabelKeys
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
2
vendor/k8s.io/kubernetes/pkg/apis/networking/register.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/apis/networking/register.go
generated
vendored
@ -52,6 +52,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
&IngressList{},
|
||||
&IngressClass{},
|
||||
&IngressClassList{},
|
||||
&ClusterCIDR{},
|
||||
&ClusterCIDRList{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
68
vendor/k8s.io/kubernetes/pkg/apis/networking/types.go
generated
vendored
68
vendor/k8s.io/kubernetes/pkg/apis/networking/types.go
generated
vendored
@ -154,8 +154,6 @@ 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 Beta state and is enabled by default.
|
||||
// It can be disabled using the Feature Gate "NetworkPolicyEndPort".
|
||||
// +optional
|
||||
EndPort *int32
|
||||
}
|
||||
@ -361,7 +359,7 @@ const (
|
||||
// IngressClassParametersReferenceScopeNamespace indicates that the
|
||||
// referenced Parameters resource is namespace-scoped.
|
||||
IngressClassParametersReferenceScopeNamespace = "Namespace"
|
||||
// IngressClassParametersReferenceScopeNamespace indicates that the
|
||||
// IngressClassParametersReferenceScopeCluster indicates that the
|
||||
// referenced Parameters resource is cluster-scoped.
|
||||
IngressClassParametersReferenceScopeCluster = "Cluster"
|
||||
)
|
||||
@ -585,3 +583,67 @@ type ServiceBackendPort struct {
|
||||
// +optional
|
||||
Number int32
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ClusterCIDR represents a single configuration for per-Node Pod CIDR
|
||||
// allocations when the MultiCIDRRangeAllocator is enabled (see the config for
|
||||
// kube-controller-manager). A cluster may have any number of ClusterCIDR
|
||||
// resources, all of which will be considered when allocating a CIDR for a
|
||||
// Node. A ClusterCIDR is eligible to be used for a given Node when the node
|
||||
// selector matches the node in question and has free CIDRs to allocate. In
|
||||
// case of multiple matching ClusterCIDR resources, the allocator will attempt
|
||||
// to break ties using internal heuristics, but any ClusterCIDR whose node
|
||||
// selector matches the Node may be used.
|
||||
type ClusterCIDR struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
|
||||
Spec ClusterCIDRSpec
|
||||
}
|
||||
|
||||
// ClusterCIDRSpec defines the desired state of ClusterCIDR.
|
||||
type ClusterCIDRSpec struct {
|
||||
// NodeSelector defines which nodes the config is applicable to.
|
||||
// An empty or nil NodeSelector selects all nodes.
|
||||
// This field is immutable.
|
||||
// +optional
|
||||
NodeSelector *api.NodeSelector
|
||||
|
||||
// PerNodeHostBits defines the number of host bits to be configured per node.
|
||||
// A subnet mask determines how much of the address is used for network bits
|
||||
// and host bits. For example an IPv4 address of 192.168.0.0/24, splits the
|
||||
// address into 24 bits for the network portion and 8 bits for the host portion.
|
||||
// To allocate 256 IPs, set this field to 8 (a /24 mask for IPv4 or a /120 for IPv6).
|
||||
// Minimum value is 4 (16 IPs).
|
||||
// This field is immutable.
|
||||
// +required
|
||||
PerNodeHostBits int32
|
||||
|
||||
// IPv4 defines an IPv4 IP block in CIDR notation(e.g. "10.0.0.0/8").
|
||||
// At least one of IPv4 and IPv6 must be specified.
|
||||
// This field is immutable.
|
||||
// +optional
|
||||
IPv4 string
|
||||
|
||||
// IPv6 defines an IPv6 IP block in CIDR notation(e.g. "fd12:3456:789a:1::/64").
|
||||
// At least one of IPv4 and IPv6 must be specified.
|
||||
// This field is immutable.
|
||||
// +optional
|
||||
IPv6 string
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ClusterCIDRList contains a list of ClusterCIDRs.
|
||||
type ClusterCIDRList struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
// +optional
|
||||
metav1.ListMeta
|
||||
|
||||
// Items is the list of ClusterCIDRs.
|
||||
Items []ClusterCIDR
|
||||
}
|
||||
|
81
vendor/k8s.io/kubernetes/pkg/apis/networking/zz_generated.deepcopy.go
generated
vendored
81
vendor/k8s.io/kubernetes/pkg/apis/networking/zz_generated.deepcopy.go
generated
vendored
@ -28,6 +28,87 @@ import (
|
||||
core "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterCIDR) DeepCopyInto(out *ClusterCIDR) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCIDR.
|
||||
func (in *ClusterCIDR) DeepCopy() *ClusterCIDR {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterCIDR)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterCIDR) 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 *ClusterCIDRList) DeepCopyInto(out *ClusterCIDRList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ClusterCIDR, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCIDRList.
|
||||
func (in *ClusterCIDRList) DeepCopy() *ClusterCIDRList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterCIDRList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterCIDRList) 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 *ClusterCIDRSpec) DeepCopyInto(out *ClusterCIDRSpec) {
|
||||
*out = *in
|
||||
if in.NodeSelector != nil {
|
||||
in, out := &in.NodeSelector, &out.NodeSelector
|
||||
*out = new(core.NodeSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCIDRSpec.
|
||||
func (in *ClusterCIDRSpec) DeepCopy() *ClusterCIDRSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterCIDRSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HTTPIngressPath) DeepCopyInto(out *HTTPIngressPath) {
|
||||
*out = *in
|
||||
|
1
vendor/k8s.io/kubernetes/pkg/apis/policy/types.go
generated
vendored
1
vendor/k8s.io/kubernetes/pkg/apis/policy/types.go
generated
vendored
@ -214,7 +214,6 @@ type PodSecurityPolicySpec struct {
|
||||
AllowedFlexVolumes []AllowedFlexVolume
|
||||
// AllowedCSIDrivers is an allowlist of inline CSI drivers that must be explicitly set to be embedded within a pod spec.
|
||||
// An empty value indicates that any CSI driver can be used for inline ephemeral volumes.
|
||||
// This is a beta field, and is only honored if the API server enables the CSIInlineVolume feature gate.
|
||||
// +optional
|
||||
AllowedCSIDrivers []AllowedCSIDriver
|
||||
// AllowedUnsafeSysctls is a list of explicitly allowed unsafe sysctls, defaults to none.
|
||||
|
3
vendor/k8s.io/kubernetes/pkg/cluster/ports/ports.go
generated
vendored
3
vendor/k8s.io/kubernetes/pkg/cluster/ports/ports.go
generated
vendored
@ -31,6 +31,9 @@ const (
|
||||
// until heapster can transition to using the SSL endpoint.
|
||||
// TODO(roberthbailey): Remove this once we have a better solution for heapster.
|
||||
KubeletReadOnlyPort = 10255
|
||||
// KubeletHealthzPort exposes a healthz endpoint from the kubelet.
|
||||
// May be overridden by a flag at startup.
|
||||
KubeletHealthzPort = 10248
|
||||
// ProxyHealthzPort is the default port for the proxy healthz server.
|
||||
// May be overridden by a flag at startup.
|
||||
ProxyHealthzPort = 10256
|
||||
|
31
vendor/k8s.io/kubernetes/pkg/controller/controller_ref_manager.go
generated
vendored
31
vendor/k8s.io/kubernetes/pkg/controller/controller_ref_manager.go
generated
vendored
@ -54,8 +54,8 @@ func (m *BaseControllerRefManager) CanAdopt(ctx context.Context) error {
|
||||
// ClaimObject tries to take ownership of an object for this controller.
|
||||
//
|
||||
// It will reconcile the following:
|
||||
// * Adopt orphans if the match function returns true.
|
||||
// * Release owned objects if the match function returns false.
|
||||
// - Adopt orphans if the match function returns true.
|
||||
// - Release owned objects if the match function returns false.
|
||||
//
|
||||
// A non-nil error is returned if some form of reconciliation was attempted and
|
||||
// failed. Usually, controllers should try again later in case reconciliation
|
||||
@ -143,8 +143,9 @@ type PodControllerRefManager struct {
|
||||
// If CanAdopt() returns a non-nil error, all adoptions will fail.
|
||||
//
|
||||
// NOTE: Once CanAdopt() is called, it will not be called again by the same
|
||||
// PodControllerRefManager instance. Create a new instance if it makes
|
||||
// sense to check CanAdopt() again (e.g. in a different sync pass).
|
||||
//
|
||||
// PodControllerRefManager instance. Create a new instance if it makes
|
||||
// sense to check CanAdopt() again (e.g. in a different sync pass).
|
||||
func NewPodControllerRefManager(
|
||||
podControl PodControlInterface,
|
||||
controller metav1.Object,
|
||||
@ -168,8 +169,8 @@ func NewPodControllerRefManager(
|
||||
// ClaimPods tries to take ownership of a list of Pods.
|
||||
//
|
||||
// It will reconcile the following:
|
||||
// * Adopt orphans if the selector matches.
|
||||
// * Release owned objects if the selector no longer matches.
|
||||
// - Adopt orphans if the selector matches.
|
||||
// - Release owned objects if the selector no longer matches.
|
||||
//
|
||||
// Optional: If one or more filters are specified, a Pod will only be claimed if
|
||||
// all filters return true.
|
||||
@ -283,8 +284,9 @@ type ReplicaSetControllerRefManager struct {
|
||||
// If CanAdopt() returns a non-nil error, all adoptions will fail.
|
||||
//
|
||||
// NOTE: Once CanAdopt() is called, it will not be called again by the same
|
||||
// ReplicaSetControllerRefManager instance. Create a new instance if it
|
||||
// makes sense to check CanAdopt() again (e.g. in a different sync pass).
|
||||
//
|
||||
// ReplicaSetControllerRefManager instance. Create a new instance if it
|
||||
// makes sense to check CanAdopt() again (e.g. in a different sync pass).
|
||||
func NewReplicaSetControllerRefManager(
|
||||
rsControl RSControlInterface,
|
||||
controller metav1.Object,
|
||||
@ -306,8 +308,8 @@ func NewReplicaSetControllerRefManager(
|
||||
// ClaimReplicaSets tries to take ownership of a list of ReplicaSets.
|
||||
//
|
||||
// It will reconcile the following:
|
||||
// * Adopt orphans if the selector matches.
|
||||
// * Release owned objects if the selector no longer matches.
|
||||
// - Adopt orphans if the selector matches.
|
||||
// - Release owned objects if the selector no longer matches.
|
||||
//
|
||||
// A non-nil error is returned if some form of reconciliation was attempted and
|
||||
// failed. Usually, controllers should try again later in case reconciliation
|
||||
@ -421,8 +423,9 @@ type ControllerRevisionControllerRefManager struct {
|
||||
// If canAdopt() returns a non-nil error, all adoptions will fail.
|
||||
//
|
||||
// NOTE: Once canAdopt() is called, it will not be called again by the same
|
||||
// ControllerRevisionControllerRefManager instance. Create a new instance if it
|
||||
// makes sense to check canAdopt() again (e.g. in a different sync pass).
|
||||
//
|
||||
// ControllerRevisionControllerRefManager instance. Create a new instance if it
|
||||
// makes sense to check canAdopt() again (e.g. in a different sync pass).
|
||||
func NewControllerRevisionControllerRefManager(
|
||||
crControl ControllerRevisionControlInterface,
|
||||
controller metav1.Object,
|
||||
@ -444,8 +447,8 @@ func NewControllerRevisionControllerRefManager(
|
||||
// ClaimControllerRevisions tries to take ownership of a list of ControllerRevisions.
|
||||
//
|
||||
// It will reconcile the following:
|
||||
// * Adopt orphans if the selector matches.
|
||||
// * Release owned objects if the selector no longer matches.
|
||||
// - Adopt orphans if the selector matches.
|
||||
// - Release owned objects if the selector no longer matches.
|
||||
//
|
||||
// A non-nil error is returned if some form of reconciliation was attempted and
|
||||
// failed. Usually, controllers should try again later in case reconciliation
|
||||
|
36
vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go
generated
vendored
36
vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go
generated
vendored
@ -755,24 +755,24 @@ func (s ActivePods) Less(i, j int) bool {
|
||||
// length. After sorting, the pods will be ordered as follows, applying each
|
||||
// rule in turn until one matches:
|
||||
//
|
||||
// 1. If only one of the pods is assigned to a node, the pod that is not
|
||||
// assigned comes before the pod that is.
|
||||
// 2. If the pods' phases differ, a pending pod comes before a pod whose phase
|
||||
// is unknown, and a pod whose phase is unknown comes before a running pod.
|
||||
// 3. If exactly one of the pods is ready, the pod that is not ready comes
|
||||
// before the ready pod.
|
||||
// 4. If controller.kubernetes.io/pod-deletion-cost annotation is set, then
|
||||
// the pod with the lower value will come first.
|
||||
// 5. If the pods' ranks differ, the pod with greater rank comes before the pod
|
||||
// with lower rank.
|
||||
// 6. If both pods are ready but have not been ready for the same amount of
|
||||
// time, the pod that has been ready for a shorter amount of time comes
|
||||
// before the pod that has been ready for longer.
|
||||
// 7. If one pod has a container that has restarted more than any container in
|
||||
// the other pod, the pod with the container with more restarts comes
|
||||
// before the other pod.
|
||||
// 8. If the pods' creation times differ, the pod that was created more recently
|
||||
// comes before the older pod.
|
||||
// 1. If only one of the pods is assigned to a node, the pod that is not
|
||||
// assigned comes before the pod that is.
|
||||
// 2. If the pods' phases differ, a pending pod comes before a pod whose phase
|
||||
// is unknown, and a pod whose phase is unknown comes before a running pod.
|
||||
// 3. If exactly one of the pods is ready, the pod that is not ready comes
|
||||
// before the ready pod.
|
||||
// 4. If controller.kubernetes.io/pod-deletion-cost annotation is set, then
|
||||
// the pod with the lower value will come first.
|
||||
// 5. If the pods' ranks differ, the pod with greater rank comes before the pod
|
||||
// with lower rank.
|
||||
// 6. If both pods are ready but have not been ready for the same amount of
|
||||
// time, the pod that has been ready for a shorter amount of time comes
|
||||
// before the pod that has been ready for longer.
|
||||
// 7. If one pod has a container that has restarted more than any container in
|
||||
// the other pod, the pod with the container with more restarts comes
|
||||
// before the other pod.
|
||||
// 8. If the pods' creation times differ, the pod that was created more recently
|
||||
// comes before the older pod.
|
||||
//
|
||||
// In 6 and 8, times are compared in a logarithmic scale. This allows a level
|
||||
// of randomness among equivalent Pods when sorting. If two pods have the same
|
||||
|
9
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util.go
generated
vendored
9
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util.go
generated
vendored
@ -302,7 +302,8 @@ var annotationsToSkip = map[string]bool{
|
||||
|
||||
// skipCopyAnnotation returns true if we should skip copying the annotation with the given annotation key
|
||||
// TODO: How to decide which annotations should / should not be copied?
|
||||
// See https://github.com/kubernetes/kubernetes/pull/20035#issuecomment-179558615
|
||||
//
|
||||
// See https://github.com/kubernetes/kubernetes/pull/20035#issuecomment-179558615
|
||||
func skipCopyAnnotation(key string) bool {
|
||||
return annotationsToSkip[key]
|
||||
}
|
||||
@ -595,9 +596,9 @@ func ListPods(deployment *apps.Deployment, rsList []*apps.ReplicaSet, getPodList
|
||||
|
||||
// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
|
||||
// We ignore pod-template-hash because:
|
||||
// 1. The hash result would be different upon podTemplateSpec API changes
|
||||
// (e.g. the addition of a new field will cause the hash code to change)
|
||||
// 2. The deployment template won't have hash labels
|
||||
// 1. The hash result would be different upon podTemplateSpec API changes
|
||||
// (e.g. the addition of a new field will cause the hash code to change)
|
||||
// 2. The deployment template won't have hash labels
|
||||
func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool {
|
||||
t1Copy := template1.DeepCopy()
|
||||
t2Copy := template2.DeepCopy()
|
||||
|
1908
vendor/k8s.io/kubernetes/pkg/features/kube_features.go
generated
vendored
1908
vendor/k8s.io/kubernetes/pkg/features/kube_features.go
generated
vendored
File diff suppressed because it is too large
Load Diff
15
vendor/k8s.io/kubernetes/pkg/fieldpath/fieldpath.go
generated
vendored
15
vendor/k8s.io/kubernetes/pkg/fieldpath/fieldpath.go
generated
vendored
@ -84,15 +84,16 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error)
|
||||
|
||||
// SplitMaybeSubscriptedPath checks whether the specified fieldPath is
|
||||
// subscripted, and
|
||||
// - if yes, this function splits the fieldPath into path and subscript, and
|
||||
// returns (path, subscript, true).
|
||||
// - if no, this function returns (fieldPath, "", false).
|
||||
// - if yes, this function splits the fieldPath into path and subscript, and
|
||||
// returns (path, subscript, true).
|
||||
// - if no, this function returns (fieldPath, "", false).
|
||||
//
|
||||
// Example inputs and outputs:
|
||||
// - "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true)
|
||||
// - "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true)
|
||||
// - "metadata.labels['']" --> ("metadata.labels", "", true)
|
||||
// - "metadata.labels" --> ("metadata.labels", "", false)
|
||||
//
|
||||
// "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true)
|
||||
// "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true)
|
||||
// "metadata.labels['']" --> ("metadata.labels", "", true)
|
||||
// "metadata.labels" --> ("metadata.labels", "", false)
|
||||
func SplitMaybeSubscriptedPath(fieldPath string) (string, string, bool) {
|
||||
if !strings.HasSuffix(fieldPath, "']") {
|
||||
return fieldPath, "", false
|
||||
|
31
vendor/k8s.io/kubernetes/pkg/kubelet/apis/config/types.go
generated
vendored
31
vendor/k8s.io/kubernetes/pkg/kubelet/apis/config/types.go
generated
vendored
@ -19,7 +19,8 @@ package config
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
componentbaseconfig "k8s.io/component-base/config"
|
||||
logsapi "k8s.io/component-base/logs/api/v1"
|
||||
tracingapi "k8s.io/component-base/tracing/api/v1"
|
||||
)
|
||||
|
||||
// HairpinMode denotes how the kubelet should configure networking to handle
|
||||
@ -352,14 +353,14 @@ type KubeletConfiguration struct {
|
||||
|
||||
/* the following fields are meant for Node Allocatable */
|
||||
|
||||
// A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=150G,pid=100) pairs
|
||||
// A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=150G,ephemeral-storage=1G,pid=100) pairs
|
||||
// that describe resources reserved for non-kubernetes components.
|
||||
// Currently only cpu and memory are supported.
|
||||
// Currently only cpu, memory and local ephemeral storage for root file system are supported.
|
||||
// See http://kubernetes.io/docs/user-guide/compute-resources for more detail.
|
||||
SystemReserved map[string]string
|
||||
// A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=150G,pid=100) pairs
|
||||
// A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=150G,ephemeral-storage=1G,pid=100) pairs
|
||||
// that describe resources reserved for kubernetes system components.
|
||||
// Currently cpu, memory and local ephemeral storage for root file system are supported.
|
||||
// Currently only cpu, memory and local ephemeral storage for root file system are supported.
|
||||
// See http://kubernetes.io/docs/user-guide/compute-resources for more detail.
|
||||
KubeReserved map[string]string
|
||||
// This flag helps kubelet identify absolute name of top level cgroup used to enforce `SystemReserved` compute resource reservation for OS system daemons.
|
||||
@ -370,7 +371,7 @@ type KubeletConfiguration struct {
|
||||
KubeReservedCgroup string
|
||||
// This flag specifies the various Node Allocatable enforcements that Kubelet needs to perform.
|
||||
// This flag accepts a list of options. Acceptable options are `pods`, `system-reserved` & `kube-reserved`.
|
||||
// Refer to [Node Allocatable](https://git.k8s.io/community/contributors/design-proposals/node/node-allocatable.md) doc for more information.
|
||||
// Refer to [Node Allocatable](https://github.com/kubernetes/design-proposals-archive/blob/main/node/node-allocatable.md) doc for more information.
|
||||
EnforceNodeAllocatable []string
|
||||
// This option specifies the cpu list reserved for the host level system threads and kubernetes related threads.
|
||||
// This provide a "static" CPU list rather than the "dynamic" list by system-reserved and kube-reserved.
|
||||
@ -384,7 +385,7 @@ type KubeletConfiguration struct {
|
||||
ShowHiddenMetricsForVersion string
|
||||
// Logging specifies the options of logging.
|
||||
// Refer [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.
|
||||
Logging componentbaseconfig.LoggingConfiguration
|
||||
Logging logsapi.LoggingConfiguration
|
||||
// EnableSystemLogHandler enables /logs handler.
|
||||
EnableSystemLogHandler bool
|
||||
// ShutdownGracePeriod specifies the total duration that the node should delay the shutdown and total grace period for pod termination during a node shutdown.
|
||||
@ -441,10 +442,24 @@ type KubeletConfiguration struct {
|
||||
// is true and upon the initial registration of the node.
|
||||
// +optional
|
||||
RegisterWithTaints []v1.Taint
|
||||
|
||||
// registerNode enables automatic registration with the apiserver.
|
||||
// +optional
|
||||
RegisterNode bool
|
||||
// Tracing specifies the versioned configuration for OpenTelemetry tracing clients.
|
||||
// See http://kep.k8s.io/2832 for more details.
|
||||
// +featureGate=KubeletTracing
|
||||
// +optional
|
||||
Tracing *tracingapi.TracingConfiguration
|
||||
|
||||
// LocalStorageCapacityIsolation enables local ephemeral storage isolation feature. The default setting is true.
|
||||
// This feature allows users to set request/limit for container's ephemeral storage and manage it in a similar way
|
||||
// as cpu and memory. It also allows setting sizeLimit for emptyDir volume, which will trigger pod eviction if disk
|
||||
// usage from the volume exceeds the limit.
|
||||
// This feature depends on the capability of detecting correct root file system disk usage. For certain systems,
|
||||
// such as kind rootless, if this capability cannot be supported, the feature LocalStorageCapacityIsolation should be
|
||||
// disabled. Once disabled, user should not set request/limit for container's ephemeral storage, or sizeLimit for emptyDir.
|
||||
// +optional
|
||||
LocalStorageCapacityIsolation bool
|
||||
}
|
||||
|
||||
// KubeletAuthorizationMode denotes the authorization mode for the kubelet
|
||||
|
6
vendor/k8s.io/kubernetes/pkg/kubelet/apis/config/zz_generated.deepcopy.go
generated
vendored
6
vendor/k8s.io/kubernetes/pkg/kubelet/apis/config/zz_generated.deepcopy.go
generated
vendored
@ -25,6 +25,7 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
apiv1 "k8s.io/component-base/tracing/api/v1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
@ -307,6 +308,11 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Tracing != nil {
|
||||
in, out := &in.Tracing, &out.Tracing
|
||||
*out = new(apiv1.TracingConfiguration)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
474
vendor/k8s.io/kubernetes/pkg/securitycontext/accessors.go
generated
vendored
Normal file
474
vendor/k8s.io/kubernetes/pkg/securitycontext/accessors.go
generated
vendored
Normal file
@ -0,0 +1,474 @@
|
||||
/*
|
||||
Copyright 2017 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 securitycontext
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// PodSecurityContextAccessor allows reading the values of a PodSecurityContext object
|
||||
type PodSecurityContextAccessor interface {
|
||||
HostNetwork() bool
|
||||
HostPID() bool
|
||||
HostIPC() bool
|
||||
SELinuxOptions() *api.SELinuxOptions
|
||||
RunAsUser() *int64
|
||||
RunAsGroup() *int64
|
||||
RunAsNonRoot() *bool
|
||||
SupplementalGroups() []int64
|
||||
FSGroup() *int64
|
||||
}
|
||||
|
||||
// PodSecurityContextMutator allows reading and writing the values of a PodSecurityContext object
|
||||
type PodSecurityContextMutator interface {
|
||||
PodSecurityContextAccessor
|
||||
|
||||
SetHostNetwork(bool)
|
||||
SetHostPID(bool)
|
||||
SetHostIPC(bool)
|
||||
SetSELinuxOptions(*api.SELinuxOptions)
|
||||
SetRunAsUser(*int64)
|
||||
SetRunAsGroup(*int64)
|
||||
SetRunAsNonRoot(*bool)
|
||||
SetSupplementalGroups([]int64)
|
||||
SetFSGroup(*int64)
|
||||
|
||||
// PodSecurityContext returns the current PodSecurityContext object
|
||||
PodSecurityContext() *api.PodSecurityContext
|
||||
}
|
||||
|
||||
// NewPodSecurityContextAccessor returns an accessor for the given pod security context.
|
||||
// May be initialized with a nil PodSecurityContext.
|
||||
func NewPodSecurityContextAccessor(podSC *api.PodSecurityContext) PodSecurityContextAccessor {
|
||||
return &podSecurityContextWrapper{podSC: podSC}
|
||||
}
|
||||
|
||||
// NewPodSecurityContextMutator returns a mutator for the given pod security context.
|
||||
// May be initialized with a nil PodSecurityContext.
|
||||
func NewPodSecurityContextMutator(podSC *api.PodSecurityContext) PodSecurityContextMutator {
|
||||
return &podSecurityContextWrapper{podSC: podSC}
|
||||
}
|
||||
|
||||
type podSecurityContextWrapper struct {
|
||||
podSC *api.PodSecurityContext
|
||||
}
|
||||
|
||||
func (w *podSecurityContextWrapper) PodSecurityContext() *api.PodSecurityContext {
|
||||
return w.podSC
|
||||
}
|
||||
|
||||
func (w *podSecurityContextWrapper) ensurePodSC() {
|
||||
if w.podSC == nil {
|
||||
w.podSC = &api.PodSecurityContext{}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *podSecurityContextWrapper) HostNetwork() bool {
|
||||
if w.podSC == nil {
|
||||
return false
|
||||
}
|
||||
return w.podSC.HostNetwork
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetHostNetwork(v bool) {
|
||||
if w.podSC == nil && v == false {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
w.podSC.HostNetwork = v
|
||||
}
|
||||
func (w *podSecurityContextWrapper) HostPID() bool {
|
||||
if w.podSC == nil {
|
||||
return false
|
||||
}
|
||||
return w.podSC.HostPID
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetHostPID(v bool) {
|
||||
if w.podSC == nil && v == false {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
w.podSC.HostPID = v
|
||||
}
|
||||
func (w *podSecurityContextWrapper) HostIPC() bool {
|
||||
if w.podSC == nil {
|
||||
return false
|
||||
}
|
||||
return w.podSC.HostIPC
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetHostIPC(v bool) {
|
||||
if w.podSC == nil && v == false {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
w.podSC.HostIPC = v
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions {
|
||||
if w.podSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.podSC.SELinuxOptions
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) {
|
||||
if w.podSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
w.podSC.SELinuxOptions = v
|
||||
}
|
||||
func (w *podSecurityContextWrapper) RunAsUser() *int64 {
|
||||
if w.podSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.podSC.RunAsUser
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetRunAsUser(v *int64) {
|
||||
if w.podSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
w.podSC.RunAsUser = v
|
||||
}
|
||||
func (w *podSecurityContextWrapper) RunAsGroup() *int64 {
|
||||
if w.podSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.podSC.RunAsGroup
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetRunAsGroup(v *int64) {
|
||||
if w.podSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
w.podSC.RunAsGroup = v
|
||||
}
|
||||
|
||||
func (w *podSecurityContextWrapper) RunAsNonRoot() *bool {
|
||||
if w.podSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.podSC.RunAsNonRoot
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetRunAsNonRoot(v *bool) {
|
||||
if w.podSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
w.podSC.RunAsNonRoot = v
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SupplementalGroups() []int64 {
|
||||
if w.podSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.podSC.SupplementalGroups
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetSupplementalGroups(v []int64) {
|
||||
if w.podSC == nil && len(v) == 0 {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
if len(v) == 0 && len(w.podSC.SupplementalGroups) == 0 {
|
||||
return
|
||||
}
|
||||
w.podSC.SupplementalGroups = v
|
||||
}
|
||||
func (w *podSecurityContextWrapper) FSGroup() *int64 {
|
||||
if w.podSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.podSC.FSGroup
|
||||
}
|
||||
func (w *podSecurityContextWrapper) SetFSGroup(v *int64) {
|
||||
if w.podSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensurePodSC()
|
||||
w.podSC.FSGroup = v
|
||||
}
|
||||
|
||||
// ContainerSecurityContextAccessor allows reading the values of a SecurityContext object
|
||||
type ContainerSecurityContextAccessor interface {
|
||||
Capabilities() *api.Capabilities
|
||||
Privileged() *bool
|
||||
ProcMount() api.ProcMountType
|
||||
SELinuxOptions() *api.SELinuxOptions
|
||||
RunAsUser() *int64
|
||||
RunAsGroup() *int64
|
||||
RunAsNonRoot() *bool
|
||||
ReadOnlyRootFilesystem() *bool
|
||||
AllowPrivilegeEscalation() *bool
|
||||
}
|
||||
|
||||
// ContainerSecurityContextMutator allows reading and writing the values of a SecurityContext object
|
||||
type ContainerSecurityContextMutator interface {
|
||||
ContainerSecurityContextAccessor
|
||||
|
||||
ContainerSecurityContext() *api.SecurityContext
|
||||
|
||||
SetCapabilities(*api.Capabilities)
|
||||
SetPrivileged(*bool)
|
||||
SetSELinuxOptions(*api.SELinuxOptions)
|
||||
SetRunAsUser(*int64)
|
||||
SetRunAsGroup(*int64)
|
||||
SetRunAsNonRoot(*bool)
|
||||
SetReadOnlyRootFilesystem(*bool)
|
||||
SetAllowPrivilegeEscalation(*bool)
|
||||
}
|
||||
|
||||
// NewContainerSecurityContextAccessor returns an accessor for the provided container security context
|
||||
// May be initialized with a nil SecurityContext
|
||||
func NewContainerSecurityContextAccessor(containerSC *api.SecurityContext) ContainerSecurityContextAccessor {
|
||||
return &containerSecurityContextWrapper{containerSC: containerSC}
|
||||
}
|
||||
|
||||
// NewContainerSecurityContextMutator returns a mutator for the provided container security context
|
||||
// May be initialized with a nil SecurityContext
|
||||
func NewContainerSecurityContextMutator(containerSC *api.SecurityContext) ContainerSecurityContextMutator {
|
||||
return &containerSecurityContextWrapper{containerSC: containerSC}
|
||||
}
|
||||
|
||||
type containerSecurityContextWrapper struct {
|
||||
containerSC *api.SecurityContext
|
||||
}
|
||||
|
||||
func (w *containerSecurityContextWrapper) ContainerSecurityContext() *api.SecurityContext {
|
||||
return w.containerSC
|
||||
}
|
||||
|
||||
func (w *containerSecurityContextWrapper) ensureContainerSC() {
|
||||
if w.containerSC == nil {
|
||||
w.containerSC = &api.SecurityContext{}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *containerSecurityContextWrapper) Capabilities() *api.Capabilities {
|
||||
if w.containerSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.containerSC.Capabilities
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SetCapabilities(v *api.Capabilities) {
|
||||
if w.containerSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensureContainerSC()
|
||||
w.containerSC.Capabilities = v
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) Privileged() *bool {
|
||||
if w.containerSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.containerSC.Privileged
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SetPrivileged(v *bool) {
|
||||
if w.containerSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensureContainerSC()
|
||||
w.containerSC.Privileged = v
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) ProcMount() api.ProcMountType {
|
||||
if w.containerSC == nil {
|
||||
return api.DefaultProcMount
|
||||
}
|
||||
if w.containerSC.ProcMount == nil {
|
||||
return api.DefaultProcMount
|
||||
}
|
||||
return *w.containerSC.ProcMount
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions {
|
||||
if w.containerSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.containerSC.SELinuxOptions
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) {
|
||||
if w.containerSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensureContainerSC()
|
||||
w.containerSC.SELinuxOptions = v
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) RunAsUser() *int64 {
|
||||
if w.containerSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.containerSC.RunAsUser
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SetRunAsUser(v *int64) {
|
||||
if w.containerSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensureContainerSC()
|
||||
w.containerSC.RunAsUser = v
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) RunAsGroup() *int64 {
|
||||
if w.containerSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.containerSC.RunAsGroup
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SetRunAsGroup(v *int64) {
|
||||
if w.containerSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensureContainerSC()
|
||||
w.containerSC.RunAsGroup = v
|
||||
}
|
||||
|
||||
func (w *containerSecurityContextWrapper) RunAsNonRoot() *bool {
|
||||
if w.containerSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.containerSC.RunAsNonRoot
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SetRunAsNonRoot(v *bool) {
|
||||
if w.containerSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensureContainerSC()
|
||||
w.containerSC.RunAsNonRoot = v
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) ReadOnlyRootFilesystem() *bool {
|
||||
if w.containerSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.containerSC.ReadOnlyRootFilesystem
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SetReadOnlyRootFilesystem(v *bool) {
|
||||
if w.containerSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensureContainerSC()
|
||||
w.containerSC.ReadOnlyRootFilesystem = v
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) AllowPrivilegeEscalation() *bool {
|
||||
if w.containerSC == nil {
|
||||
return nil
|
||||
}
|
||||
return w.containerSC.AllowPrivilegeEscalation
|
||||
}
|
||||
func (w *containerSecurityContextWrapper) SetAllowPrivilegeEscalation(v *bool) {
|
||||
if w.containerSC == nil && v == nil {
|
||||
return
|
||||
}
|
||||
w.ensureContainerSC()
|
||||
w.containerSC.AllowPrivilegeEscalation = v
|
||||
}
|
||||
|
||||
// NewEffectiveContainerSecurityContextAccessor returns an accessor for reading effective values
|
||||
// for the provided pod security context and container security context
|
||||
func NewEffectiveContainerSecurityContextAccessor(podSC PodSecurityContextAccessor, containerSC ContainerSecurityContextMutator) ContainerSecurityContextAccessor {
|
||||
return &effectiveContainerSecurityContextWrapper{podSC: podSC, containerSC: containerSC}
|
||||
}
|
||||
|
||||
// NewEffectiveContainerSecurityContextMutator returns a mutator for reading and writing effective values
|
||||
// for the provided pod security context and container security context
|
||||
func NewEffectiveContainerSecurityContextMutator(podSC PodSecurityContextAccessor, containerSC ContainerSecurityContextMutator) ContainerSecurityContextMutator {
|
||||
return &effectiveContainerSecurityContextWrapper{podSC: podSC, containerSC: containerSC}
|
||||
}
|
||||
|
||||
type effectiveContainerSecurityContextWrapper struct {
|
||||
podSC PodSecurityContextAccessor
|
||||
containerSC ContainerSecurityContextMutator
|
||||
}
|
||||
|
||||
func (w *effectiveContainerSecurityContextWrapper) ContainerSecurityContext() *api.SecurityContext {
|
||||
return w.containerSC.ContainerSecurityContext()
|
||||
}
|
||||
|
||||
func (w *effectiveContainerSecurityContextWrapper) Capabilities() *api.Capabilities {
|
||||
return w.containerSC.Capabilities()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SetCapabilities(v *api.Capabilities) {
|
||||
if !reflect.DeepEqual(w.Capabilities(), v) {
|
||||
w.containerSC.SetCapabilities(v)
|
||||
}
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) Privileged() *bool {
|
||||
return w.containerSC.Privileged()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SetPrivileged(v *bool) {
|
||||
if !reflect.DeepEqual(w.Privileged(), v) {
|
||||
w.containerSC.SetPrivileged(v)
|
||||
}
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) ProcMount() api.ProcMountType {
|
||||
return w.containerSC.ProcMount()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions {
|
||||
if v := w.containerSC.SELinuxOptions(); v != nil {
|
||||
return v
|
||||
}
|
||||
return w.podSC.SELinuxOptions()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) {
|
||||
if !reflect.DeepEqual(w.SELinuxOptions(), v) {
|
||||
w.containerSC.SetSELinuxOptions(v)
|
||||
}
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) RunAsUser() *int64 {
|
||||
if v := w.containerSC.RunAsUser(); v != nil {
|
||||
return v
|
||||
}
|
||||
return w.podSC.RunAsUser()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SetRunAsUser(v *int64) {
|
||||
if !reflect.DeepEqual(w.RunAsUser(), v) {
|
||||
w.containerSC.SetRunAsUser(v)
|
||||
}
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) RunAsGroup() *int64 {
|
||||
if v := w.containerSC.RunAsGroup(); v != nil {
|
||||
return v
|
||||
}
|
||||
return w.podSC.RunAsGroup()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SetRunAsGroup(v *int64) {
|
||||
if !reflect.DeepEqual(w.RunAsGroup(), v) {
|
||||
w.containerSC.SetRunAsGroup(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *effectiveContainerSecurityContextWrapper) RunAsNonRoot() *bool {
|
||||
if v := w.containerSC.RunAsNonRoot(); v != nil {
|
||||
return v
|
||||
}
|
||||
return w.podSC.RunAsNonRoot()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SetRunAsNonRoot(v *bool) {
|
||||
if !reflect.DeepEqual(w.RunAsNonRoot(), v) {
|
||||
w.containerSC.SetRunAsNonRoot(v)
|
||||
}
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) ReadOnlyRootFilesystem() *bool {
|
||||
return w.containerSC.ReadOnlyRootFilesystem()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SetReadOnlyRootFilesystem(v *bool) {
|
||||
if !reflect.DeepEqual(w.ReadOnlyRootFilesystem(), v) {
|
||||
w.containerSC.SetReadOnlyRootFilesystem(v)
|
||||
}
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) AllowPrivilegeEscalation() *bool {
|
||||
return w.containerSC.AllowPrivilegeEscalation()
|
||||
}
|
||||
func (w *effectiveContainerSecurityContextWrapper) SetAllowPrivilegeEscalation(v *bool) {
|
||||
if !reflect.DeepEqual(w.AllowPrivilegeEscalation(), v) {
|
||||
w.containerSC.SetAllowPrivilegeEscalation(v)
|
||||
}
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/securitycontext/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/securitycontext/doc.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2014 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 securitycontext contains security context api implementations
|
||||
package securitycontext // import "k8s.io/kubernetes/pkg/securitycontext"
|
46
vendor/k8s.io/kubernetes/pkg/securitycontext/fake.go
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/pkg/securitycontext/fake.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2014 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 securitycontext
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// ValidSecurityContextWithContainerDefaults creates a valid security context provider based on
|
||||
// empty container defaults. Used for testing.
|
||||
func ValidSecurityContextWithContainerDefaults() *v1.SecurityContext {
|
||||
priv := false
|
||||
defProcMount := v1.DefaultProcMount
|
||||
return &v1.SecurityContext{
|
||||
Capabilities: &v1.Capabilities{},
|
||||
Privileged: &priv,
|
||||
ProcMount: &defProcMount,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidInternalSecurityContextWithContainerDefaults creates a valid security context provider based on
|
||||
// empty container defaults. Used for testing.
|
||||
func ValidInternalSecurityContextWithContainerDefaults() *api.SecurityContext {
|
||||
priv := false
|
||||
dpm := api.DefaultProcMount
|
||||
return &api.SecurityContext{
|
||||
Capabilities: &api.Capabilities{},
|
||||
Privileged: &priv,
|
||||
ProcMount: &dpm,
|
||||
}
|
||||
}
|
260
vendor/k8s.io/kubernetes/pkg/securitycontext/util.go
generated
vendored
Normal file
260
vendor/k8s.io/kubernetes/pkg/securitycontext/util.go
generated
vendored
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
Copyright 2014 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 securitycontext
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account
|
||||
// the possibility of nils
|
||||
func HasPrivilegedRequest(container *v1.Container) bool {
|
||||
if container.SecurityContext == nil {
|
||||
return false
|
||||
}
|
||||
if container.SecurityContext.Privileged == nil {
|
||||
return false
|
||||
}
|
||||
return *container.SecurityContext.Privileged
|
||||
}
|
||||
|
||||
// HasCapabilitiesRequest returns true if Adds or Drops are defined in the security context
|
||||
// capabilities, taking into account nils
|
||||
func HasCapabilitiesRequest(container *v1.Container) bool {
|
||||
if container.SecurityContext == nil {
|
||||
return false
|
||||
}
|
||||
if container.SecurityContext.Capabilities == nil {
|
||||
return false
|
||||
}
|
||||
return len(container.SecurityContext.Capabilities.Add) > 0 || len(container.SecurityContext.Capabilities.Drop) > 0
|
||||
}
|
||||
|
||||
// HasWindowsHostProcessRequest returns true if container should run as HostProcess container,
|
||||
// taking into account nils
|
||||
func HasWindowsHostProcessRequest(pod *v1.Pod, container *v1.Container) bool {
|
||||
effectiveSc := DetermineEffectiveSecurityContext(pod, container)
|
||||
|
||||
if effectiveSc.WindowsOptions == nil {
|
||||
return false
|
||||
}
|
||||
if effectiveSc.WindowsOptions.HostProcess == nil {
|
||||
return false
|
||||
}
|
||||
return *effectiveSc.WindowsOptions.HostProcess
|
||||
}
|
||||
|
||||
// DetermineEffectiveSecurityContext returns a synthesized SecurityContext for reading effective configurations
|
||||
// from the provided pod's and container's security context. Container's fields take precedence in cases where both
|
||||
// are set
|
||||
func DetermineEffectiveSecurityContext(pod *v1.Pod, container *v1.Container) *v1.SecurityContext {
|
||||
effectiveSc := securityContextFromPodSecurityContext(pod)
|
||||
containerSc := container.SecurityContext
|
||||
|
||||
if effectiveSc == nil && containerSc == nil {
|
||||
return &v1.SecurityContext{}
|
||||
}
|
||||
if effectiveSc != nil && containerSc == nil {
|
||||
return effectiveSc
|
||||
}
|
||||
if effectiveSc == nil && containerSc != nil {
|
||||
return containerSc
|
||||
}
|
||||
|
||||
if containerSc.SELinuxOptions != nil {
|
||||
effectiveSc.SELinuxOptions = new(v1.SELinuxOptions)
|
||||
*effectiveSc.SELinuxOptions = *containerSc.SELinuxOptions
|
||||
}
|
||||
|
||||
if containerSc.WindowsOptions != nil {
|
||||
// only override fields that are set at the container level, not the whole thing
|
||||
if effectiveSc.WindowsOptions == nil {
|
||||
effectiveSc.WindowsOptions = &v1.WindowsSecurityContextOptions{}
|
||||
}
|
||||
if containerSc.WindowsOptions.GMSACredentialSpecName != nil || containerSc.WindowsOptions.GMSACredentialSpec != nil {
|
||||
// both GMSA fields go hand in hand
|
||||
effectiveSc.WindowsOptions.GMSACredentialSpecName = containerSc.WindowsOptions.GMSACredentialSpecName
|
||||
effectiveSc.WindowsOptions.GMSACredentialSpec = containerSc.WindowsOptions.GMSACredentialSpec
|
||||
}
|
||||
if containerSc.WindowsOptions.RunAsUserName != nil {
|
||||
effectiveSc.WindowsOptions.RunAsUserName = containerSc.WindowsOptions.RunAsUserName
|
||||
}
|
||||
if containerSc.WindowsOptions.HostProcess != nil {
|
||||
effectiveSc.WindowsOptions.HostProcess = containerSc.WindowsOptions.HostProcess
|
||||
}
|
||||
}
|
||||
|
||||
if containerSc.Capabilities != nil {
|
||||
effectiveSc.Capabilities = new(v1.Capabilities)
|
||||
*effectiveSc.Capabilities = *containerSc.Capabilities
|
||||
}
|
||||
|
||||
if containerSc.Privileged != nil {
|
||||
effectiveSc.Privileged = new(bool)
|
||||
*effectiveSc.Privileged = *containerSc.Privileged
|
||||
}
|
||||
|
||||
if containerSc.RunAsUser != nil {
|
||||
effectiveSc.RunAsUser = new(int64)
|
||||
*effectiveSc.RunAsUser = *containerSc.RunAsUser
|
||||
}
|
||||
|
||||
if containerSc.RunAsGroup != nil {
|
||||
effectiveSc.RunAsGroup = new(int64)
|
||||
*effectiveSc.RunAsGroup = *containerSc.RunAsGroup
|
||||
}
|
||||
|
||||
if containerSc.RunAsNonRoot != nil {
|
||||
effectiveSc.RunAsNonRoot = new(bool)
|
||||
*effectiveSc.RunAsNonRoot = *containerSc.RunAsNonRoot
|
||||
}
|
||||
|
||||
if containerSc.ReadOnlyRootFilesystem != nil {
|
||||
effectiveSc.ReadOnlyRootFilesystem = new(bool)
|
||||
*effectiveSc.ReadOnlyRootFilesystem = *containerSc.ReadOnlyRootFilesystem
|
||||
}
|
||||
|
||||
if containerSc.AllowPrivilegeEscalation != nil {
|
||||
effectiveSc.AllowPrivilegeEscalation = new(bool)
|
||||
*effectiveSc.AllowPrivilegeEscalation = *containerSc.AllowPrivilegeEscalation
|
||||
}
|
||||
|
||||
if containerSc.ProcMount != nil {
|
||||
effectiveSc.ProcMount = new(v1.ProcMountType)
|
||||
*effectiveSc.ProcMount = *containerSc.ProcMount
|
||||
}
|
||||
|
||||
return effectiveSc
|
||||
}
|
||||
|
||||
// DetermineEffectiveRunAsUser returns a pointer of UID from the provided pod's
|
||||
// and container's security context and a bool value to indicate if it is absent.
|
||||
// Container's runAsUser take precedence in cases where both are set.
|
||||
func DetermineEffectiveRunAsUser(pod *v1.Pod, container *v1.Container) (*int64, bool) {
|
||||
var runAsUser *int64
|
||||
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.RunAsUser != nil {
|
||||
runAsUser = new(int64)
|
||||
*runAsUser = *pod.Spec.SecurityContext.RunAsUser
|
||||
}
|
||||
if container.SecurityContext != nil && container.SecurityContext.RunAsUser != nil {
|
||||
runAsUser = new(int64)
|
||||
*runAsUser = *container.SecurityContext.RunAsUser
|
||||
}
|
||||
if runAsUser == nil {
|
||||
return nil, false
|
||||
}
|
||||
return runAsUser, true
|
||||
}
|
||||
|
||||
func securityContextFromPodSecurityContext(pod *v1.Pod) *v1.SecurityContext {
|
||||
if pod.Spec.SecurityContext == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
synthesized := &v1.SecurityContext{}
|
||||
|
||||
if pod.Spec.SecurityContext.SELinuxOptions != nil {
|
||||
synthesized.SELinuxOptions = &v1.SELinuxOptions{}
|
||||
*synthesized.SELinuxOptions = *pod.Spec.SecurityContext.SELinuxOptions
|
||||
}
|
||||
|
||||
if pod.Spec.SecurityContext.WindowsOptions != nil {
|
||||
synthesized.WindowsOptions = &v1.WindowsSecurityContextOptions{}
|
||||
*synthesized.WindowsOptions = *pod.Spec.SecurityContext.WindowsOptions
|
||||
}
|
||||
|
||||
if pod.Spec.SecurityContext.RunAsUser != nil {
|
||||
synthesized.RunAsUser = new(int64)
|
||||
*synthesized.RunAsUser = *pod.Spec.SecurityContext.RunAsUser
|
||||
}
|
||||
|
||||
if pod.Spec.SecurityContext.RunAsGroup != nil {
|
||||
synthesized.RunAsGroup = new(int64)
|
||||
*synthesized.RunAsGroup = *pod.Spec.SecurityContext.RunAsGroup
|
||||
}
|
||||
|
||||
if pod.Spec.SecurityContext.RunAsNonRoot != nil {
|
||||
synthesized.RunAsNonRoot = new(bool)
|
||||
*synthesized.RunAsNonRoot = *pod.Spec.SecurityContext.RunAsNonRoot
|
||||
}
|
||||
|
||||
return synthesized
|
||||
}
|
||||
|
||||
// AddNoNewPrivileges returns if we should add the no_new_privs option.
|
||||
func AddNoNewPrivileges(sc *v1.SecurityContext) bool {
|
||||
if sc == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// handle the case where the user did not set the default and did not explicitly set allowPrivilegeEscalation
|
||||
if sc.AllowPrivilegeEscalation == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// handle the case where defaultAllowPrivilegeEscalation is false or the user explicitly set allowPrivilegeEscalation to true/false
|
||||
return !*sc.AllowPrivilegeEscalation
|
||||
}
|
||||
|
||||
var (
|
||||
// These *must* be kept in sync with moby/moby.
|
||||
// https://github.com/moby/moby/blob/master/oci/defaults.go#L116-L134
|
||||
// @jessfraz will watch changes to those files upstream.
|
||||
defaultMaskedPaths = []string{
|
||||
"/proc/acpi",
|
||||
"/proc/kcore",
|
||||
"/proc/keys",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_list",
|
||||
"/proc/timer_stats",
|
||||
"/proc/sched_debug",
|
||||
"/proc/scsi",
|
||||
"/sys/firmware",
|
||||
}
|
||||
defaultReadonlyPaths = []string{
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger",
|
||||
}
|
||||
)
|
||||
|
||||
// ConvertToRuntimeMaskedPaths converts the ProcMountType to the specified or default
|
||||
// masked paths.
|
||||
func ConvertToRuntimeMaskedPaths(opt *v1.ProcMountType) []string {
|
||||
if opt != nil && *opt == v1.UnmaskedProcMount {
|
||||
// Unmasked proc mount should have no paths set as masked.
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Otherwise, add the default masked paths to the runtime security context.
|
||||
return defaultMaskedPaths
|
||||
}
|
||||
|
||||
// ConvertToRuntimeReadonlyPaths converts the ProcMountType to the specified or default
|
||||
// readonly paths.
|
||||
func ConvertToRuntimeReadonlyPaths(opt *v1.ProcMountType) []string {
|
||||
if opt != nil && *opt == v1.UnmaskedProcMount {
|
||||
// Unmasked proc mount should have no paths set as readonly.
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Otherwise, add the default readonly paths to the runtime security context.
|
||||
return defaultReadonlyPaths
|
||||
}
|
4
vendor/k8s.io/kubernetes/pkg/volume/noop_expandable_plugin.go
generated
vendored
4
vendor/k8s.io/kubernetes/pkg/volume/noop_expandable_plugin.go
generated
vendored
@ -75,3 +75,7 @@ func (n *noopExpandableVolumePluginInstance) SupportsBulkVolumeVerification() bo
|
||||
func (n *noopExpandableVolumePluginInstance) RequiresFSResize() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *noopExpandableVolumePluginInstance) SupportsSELinuxContextMount(spec *Spec) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
41
vendor/k8s.io/kubernetes/pkg/volume/plugins.go
generated
vendored
41
vendor/k8s.io/kubernetes/pkg/volume/plugins.go
generated
vendored
@ -70,6 +70,7 @@ var (
|
||||
"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",
|
||||
"kubernetes.io/glusterfs": "The GlusterFS volume provider is deprecated and will be removed soon after in a subsequent release",
|
||||
}
|
||||
)
|
||||
|
||||
@ -186,6 +187,10 @@ type VolumePlugin interface {
|
||||
// of enabling bulk polling of all nodes. This can speed up verification of
|
||||
// attached volumes by quite a bit, but underlying pluging must support it.
|
||||
SupportsBulkVolumeVerification() bool
|
||||
|
||||
// SupportsSELinuxContextMount returns true if volume plugins supports
|
||||
// mount -o context=XYZ for a given volume.
|
||||
SupportsSELinuxContextMount(spec *Spec) (bool, error)
|
||||
}
|
||||
|
||||
// PersistentVolumePlugin is an extended interface of VolumePlugin and is used
|
||||
@ -339,6 +344,13 @@ type KubeletVolumeHost interface {
|
||||
WaitForCacheSync() error
|
||||
// Returns hostutil.HostUtils
|
||||
GetHostUtil() hostutil.HostUtils
|
||||
// GetHostIDsForPod if the pod uses user namespaces, takes the uid and
|
||||
// gid inside the container and returns the host UID and GID those are
|
||||
// mapped to on the host. If containerUID/containerGID is nil, then it
|
||||
// returns the host UID/GID for ID 0 inside the container.
|
||||
// If the pod is not using user namespaces, as there is no mapping needed, the
|
||||
// same containerUID and containerGID params are returned.
|
||||
GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error)
|
||||
}
|
||||
|
||||
// AttachDetachVolumeHost is a AttachDetach Controller specific interface that plugins can use
|
||||
@ -662,34 +674,33 @@ func (pm *VolumePluginMgr) FindPluginBySpec(spec *Spec) (VolumePlugin, error) {
|
||||
return nil, fmt.Errorf("could not find plugin because volume spec is nil")
|
||||
}
|
||||
|
||||
matches := []VolumePlugin{}
|
||||
var match VolumePlugin
|
||||
matchedPluginNames := []string{}
|
||||
for _, v := range pm.plugins {
|
||||
if v.CanSupport(spec) {
|
||||
matches = append(matches, v)
|
||||
match = v
|
||||
matchedPluginNames = append(matchedPluginNames, v.GetPluginName())
|
||||
}
|
||||
}
|
||||
|
||||
pm.refreshProbedPlugins()
|
||||
for _, plugin := range pm.probedPlugins {
|
||||
if plugin.CanSupport(spec) {
|
||||
matches = append(matches, plugin)
|
||||
match = plugin
|
||||
matchedPluginNames = append(matchedPluginNames, plugin.GetPluginName())
|
||||
}
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
if len(matchedPluginNames) == 0 {
|
||||
return nil, fmt.Errorf("no volume plugin matched")
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
matchedPluginNames := []string{}
|
||||
for _, plugin := range matches {
|
||||
matchedPluginNames = append(matchedPluginNames, plugin.GetPluginName())
|
||||
}
|
||||
if len(matchedPluginNames) > 1 {
|
||||
return nil, fmt.Errorf("multiple volume plugins matched: %s", strings.Join(matchedPluginNames, ","))
|
||||
}
|
||||
|
||||
// Issue warning if the matched provider is deprecated
|
||||
pm.logDeprecation(matches[0].GetPluginName())
|
||||
return matches[0], nil
|
||||
pm.logDeprecation(match.GetPluginName())
|
||||
return match, nil
|
||||
}
|
||||
|
||||
// FindPluginByName fetches a plugin by name or by legacy name. If no plugin
|
||||
@ -1044,11 +1055,11 @@ func (pm *VolumePluginMgr) Run(stopCh <-chan struct{}) {
|
||||
// plugin implementations. The following attributes can be overridden per
|
||||
// plugin via configuration:
|
||||
//
|
||||
// 1. pod.Spec.Volumes[0].VolumeSource must be overridden. Recycler
|
||||
// 1. pod.Spec.Volumes[0].VolumeSource must be overridden. Recycler
|
||||
// implementations without a valid VolumeSource will fail.
|
||||
// 2. pod.GenerateName helps distinguish recycler pods by name. Recommended.
|
||||
// 2. pod.GenerateName helps distinguish recycler pods by name. Recommended.
|
||||
// Default is "pv-recycler-".
|
||||
// 3. pod.Spec.ActiveDeadlineSeconds gives the recycler pod a maximum timeout
|
||||
// 3. pod.Spec.ActiveDeadlineSeconds gives the recycler pod a maximum timeout
|
||||
// before failing. Recommended. Default is 60 seconds.
|
||||
//
|
||||
// See HostPath and NFS for working recycler examples
|
||||
@ -1074,7 +1085,7 @@ func NewPersistentVolumeRecyclerPodTemplate() *v1.Pod {
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "pv-recycler",
|
||||
Image: "k8s.gcr.io/debian-base:v2.0.0",
|
||||
Image: "registry.k8s.io/debian-base:v2.0.0",
|
||||
Command: []string{"/bin/sh"},
|
||||
Args: []string{"-c", "test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/* && test -z \"$(ls -A /scrub)\" || exit 1"},
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
|
468
vendor/k8s.io/kubernetes/pkg/volume/util/atomic_writer.go
generated
vendored
Normal file
468
vendor/k8s.io/kubernetes/pkg/volume/util/atomic_writer.go
generated
vendored
Normal file
@ -0,0 +1,468 @@
|
||||
/*
|
||||
Copyright 2016 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 util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
maxFileNameLength = 255
|
||||
maxPathLength = 4096
|
||||
)
|
||||
|
||||
// AtomicWriter handles atomically projecting content for a set of files into
|
||||
// a target directory.
|
||||
//
|
||||
// Note:
|
||||
//
|
||||
// 1. AtomicWriter reserves the set of pathnames starting with `..`.
|
||||
// 2. AtomicWriter offers no concurrency guarantees and must be synchronized
|
||||
// by the caller.
|
||||
//
|
||||
// The visible files in this volume are symlinks to files in the writer's data
|
||||
// directory. Actual files are stored in a hidden timestamped directory which
|
||||
// is symlinked to by the data directory. The timestamped directory and
|
||||
// data directory symlink are created in the writer's target dir. This scheme
|
||||
// allows the files to be atomically updated by changing the target of the
|
||||
// data directory symlink.
|
||||
//
|
||||
// Consumers of the target directory can monitor the ..data symlink using
|
||||
// inotify or fanotify to receive events when the content in the volume is
|
||||
// updated.
|
||||
type AtomicWriter struct {
|
||||
targetDir string
|
||||
logContext string
|
||||
}
|
||||
|
||||
// FileProjection contains file Data and access Mode
|
||||
type FileProjection struct {
|
||||
Data []byte
|
||||
Mode int32
|
||||
FsUser *int64
|
||||
}
|
||||
|
||||
// NewAtomicWriter creates a new AtomicWriter configured to write to the given
|
||||
// target directory, or returns an error if the target directory does not exist.
|
||||
func NewAtomicWriter(targetDir string, logContext string) (*AtomicWriter, error) {
|
||||
_, err := os.Stat(targetDir)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AtomicWriter{targetDir: targetDir, logContext: logContext}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
dataDirName = "..data"
|
||||
newDataDirName = "..data_tmp"
|
||||
)
|
||||
|
||||
// Write does an atomic projection of the given payload into the writer's target
|
||||
// directory. Input paths must not begin with '..'.
|
||||
//
|
||||
// The Write algorithm is:
|
||||
//
|
||||
// 1. The payload is validated; if the payload is invalid, the function returns
|
||||
// 2. The current timestamped directory is detected by reading the data directory
|
||||
// symlink
|
||||
//
|
||||
// 3. The old version of the volume is walked to determine whether any
|
||||
// portion of the payload was deleted and is still present on disk.
|
||||
//
|
||||
// 4. The data in the current timestamped directory is compared to the projected
|
||||
// data to determine if an update is required.
|
||||
// 5. A new timestamped dir is created
|
||||
//
|
||||
// 6. The payload is written to the new timestamped directory
|
||||
// 7. A symlink to the new timestamped directory ..data_tmp is created that will
|
||||
// become the new data directory
|
||||
// 8. The new data directory symlink is renamed to the data directory; rename is atomic
|
||||
// 9. Symlinks and directory for new user-visible files are created (if needed).
|
||||
//
|
||||
// For example, consider the files:
|
||||
// <target-dir>/podName
|
||||
// <target-dir>/user/labels
|
||||
// <target-dir>/k8s/annotations
|
||||
//
|
||||
// The user visible files are symbolic links into the internal data directory:
|
||||
// <target-dir>/podName -> ..data/podName
|
||||
// <target-dir>/usr -> ..data/usr
|
||||
// <target-dir>/k8s -> ..data/k8s
|
||||
//
|
||||
// The data directory itself is a link to a timestamped directory with
|
||||
// the real data:
|
||||
// <target-dir>/..data -> ..2016_02_01_15_04_05.12345678/
|
||||
// NOTE(claudiub): We need to create these symlinks AFTER we've finished creating and
|
||||
// linking everything else. On Windows, if a target does not exist, the created symlink
|
||||
// will not work properly if the target ends up being a directory.
|
||||
//
|
||||
// 10. Old paths are removed from the user-visible portion of the target directory
|
||||
// 11. The previous timestamped directory is removed, if it exists
|
||||
func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
|
||||
// (1)
|
||||
cleanPayload, err := validatePayload(payload)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: invalid payload: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (2)
|
||||
dataDirPath := filepath.Join(w.targetDir, dataDirName)
|
||||
oldTsDir, err := os.Readlink(dataDirPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
klog.Errorf("%s: error reading link for data directory: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
// although Readlink() returns "" on err, don't be fragile by relying on it (since it's not specified in docs)
|
||||
// empty oldTsDir indicates that it didn't exist
|
||||
oldTsDir = ""
|
||||
}
|
||||
oldTsPath := filepath.Join(w.targetDir, oldTsDir)
|
||||
|
||||
var pathsToRemove sets.String
|
||||
// if there was no old version, there's nothing to remove
|
||||
if len(oldTsDir) != 0 {
|
||||
// (3)
|
||||
pathsToRemove, err = w.pathsToRemove(cleanPayload, oldTsPath)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: error determining user-visible files to remove: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (4)
|
||||
if should, err := shouldWritePayload(cleanPayload, oldTsPath); err != nil {
|
||||
klog.Errorf("%s: error determining whether payload should be written to disk: %v", w.logContext, err)
|
||||
return err
|
||||
} else if !should && len(pathsToRemove) == 0 {
|
||||
klog.V(4).Infof("%s: no update required for target directory %v", w.logContext, w.targetDir)
|
||||
return nil
|
||||
} else {
|
||||
klog.V(4).Infof("%s: write required for target directory %v", w.logContext, w.targetDir)
|
||||
}
|
||||
}
|
||||
|
||||
// (5)
|
||||
tsDir, err := w.newTimestampDir()
|
||||
if err != nil {
|
||||
klog.V(4).Infof("%s: error creating new ts data directory: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
tsDirName := filepath.Base(tsDir)
|
||||
|
||||
// (6)
|
||||
if err = w.writePayloadToDir(cleanPayload, tsDir); err != nil {
|
||||
klog.Errorf("%s: error writing payload to ts data directory %s: %v", w.logContext, tsDir, err)
|
||||
return err
|
||||
}
|
||||
klog.V(4).Infof("%s: performed write of new data to ts data directory: %s", w.logContext, tsDir)
|
||||
|
||||
// (7)
|
||||
newDataDirPath := filepath.Join(w.targetDir, newDataDirName)
|
||||
if err = os.Symlink(tsDirName, newDataDirPath); err != nil {
|
||||
os.RemoveAll(tsDir)
|
||||
klog.Errorf("%s: error creating symbolic link for atomic update: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (8)
|
||||
if runtime.GOOS == "windows" {
|
||||
os.Remove(dataDirPath)
|
||||
err = os.Symlink(tsDirName, dataDirPath)
|
||||
os.Remove(newDataDirPath)
|
||||
} else {
|
||||
err = os.Rename(newDataDirPath, dataDirPath)
|
||||
}
|
||||
if err != nil {
|
||||
os.Remove(newDataDirPath)
|
||||
os.RemoveAll(tsDir)
|
||||
klog.Errorf("%s: error renaming symbolic link for data directory %s: %v", w.logContext, newDataDirPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (9)
|
||||
if err = w.createUserVisibleFiles(cleanPayload); err != nil {
|
||||
klog.Errorf("%s: error creating visible symlinks in %s: %v", w.logContext, w.targetDir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (10)
|
||||
if err = w.removeUserVisiblePaths(pathsToRemove); err != nil {
|
||||
klog.Errorf("%s: error removing old visible symlinks: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (11)
|
||||
if len(oldTsDir) > 0 {
|
||||
if err = os.RemoveAll(oldTsPath); err != nil {
|
||||
klog.Errorf("%s: error removing old data directory %s: %v", w.logContext, oldTsDir, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePayload returns an error if any path in the payload returns a copy of the payload with the paths cleaned.
|
||||
func validatePayload(payload map[string]FileProjection) (map[string]FileProjection, error) {
|
||||
cleanPayload := make(map[string]FileProjection)
|
||||
for k, content := range payload {
|
||||
if err := validatePath(k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanPayload[filepath.Clean(k)] = content
|
||||
}
|
||||
|
||||
return cleanPayload, nil
|
||||
}
|
||||
|
||||
// validatePath validates a single path, returning an error if the path is
|
||||
// invalid. paths may not:
|
||||
//
|
||||
// 1. be absolute
|
||||
// 2. contain '..' as an element
|
||||
// 3. start with '..'
|
||||
// 4. contain filenames larger than 255 characters
|
||||
// 5. be longer than 4096 characters
|
||||
func validatePath(targetPath string) error {
|
||||
// TODO: somehow unify this with the similar api validation,
|
||||
// validateVolumeSourcePath; the error semantics are just different enough
|
||||
// from this that it was time-prohibitive trying to find the right
|
||||
// refactoring to re-use.
|
||||
if targetPath == "" {
|
||||
return fmt.Errorf("invalid path: must not be empty: %q", targetPath)
|
||||
}
|
||||
if path.IsAbs(targetPath) {
|
||||
return fmt.Errorf("invalid path: must be relative path: %s", targetPath)
|
||||
}
|
||||
|
||||
if len(targetPath) > maxPathLength {
|
||||
return fmt.Errorf("invalid path: must be less than or equal to %d characters", maxPathLength)
|
||||
}
|
||||
|
||||
items := strings.Split(targetPath, string(os.PathSeparator))
|
||||
for _, item := range items {
|
||||
if item == ".." {
|
||||
return fmt.Errorf("invalid path: must not contain '..': %s", targetPath)
|
||||
}
|
||||
if len(item) > maxFileNameLength {
|
||||
return fmt.Errorf("invalid path: filenames must be less than or equal to %d characters", maxFileNameLength)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(items[0], "..") && len(items[0]) > 2 {
|
||||
return fmt.Errorf("invalid path: must not start with '..': %s", targetPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldWritePayload returns whether the payload should be written to disk.
|
||||
func shouldWritePayload(payload map[string]FileProjection, oldTsDir string) (bool, error) {
|
||||
for userVisiblePath, fileProjection := range payload {
|
||||
shouldWrite, err := shouldWriteFile(filepath.Join(oldTsDir, userVisiblePath), fileProjection.Data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if shouldWrite {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// shouldWriteFile returns whether a new version of a file should be written to disk.
|
||||
func shouldWriteFile(path string, content []byte) (bool, error) {
|
||||
_, err := os.Lstat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
contentOnFs, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !bytes.Equal(content, contentOnFs), nil
|
||||
}
|
||||
|
||||
// pathsToRemove walks the current version of the data directory and
|
||||
// determines which paths should be removed (if any) after the payload is
|
||||
// written to the target directory.
|
||||
func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection, oldTsDir string) (sets.String, error) {
|
||||
paths := sets.NewString()
|
||||
visitor := func(path string, info os.FileInfo, err error) error {
|
||||
relativePath := strings.TrimPrefix(path, oldTsDir)
|
||||
relativePath = strings.TrimPrefix(relativePath, string(os.PathSeparator))
|
||||
if relativePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
paths.Insert(relativePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := filepath.Walk(oldTsDir, visitor)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klog.V(5).Infof("%s: current paths: %+v", w.targetDir, paths.List())
|
||||
|
||||
newPaths := sets.NewString()
|
||||
for file := range payload {
|
||||
// add all subpaths for the payload to the set of new paths
|
||||
// to avoid attempting to remove non-empty dirs
|
||||
for subPath := file; subPath != ""; {
|
||||
newPaths.Insert(subPath)
|
||||
subPath, _ = filepath.Split(subPath)
|
||||
subPath = strings.TrimSuffix(subPath, string(os.PathSeparator))
|
||||
}
|
||||
}
|
||||
klog.V(5).Infof("%s: new paths: %+v", w.targetDir, newPaths.List())
|
||||
|
||||
result := paths.Difference(newPaths)
|
||||
klog.V(5).Infof("%s: paths to remove: %+v", w.targetDir, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// newTimestampDir creates a new timestamp directory
|
||||
func (w *AtomicWriter) newTimestampDir() (string, error) {
|
||||
tsDir, err := ioutil.TempDir(w.targetDir, time.Now().UTC().Format("..2006_01_02_15_04_05."))
|
||||
if err != nil {
|
||||
klog.Errorf("%s: unable to create new temp directory: %v", w.logContext, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 0755 permissions are needed to allow 'group' and 'other' to recurse the
|
||||
// directory tree. do a chmod here to ensure that permissions are set correctly
|
||||
// regardless of the process' umask.
|
||||
err = os.Chmod(tsDir, 0755)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: unable to set mode on new temp directory: %v", w.logContext, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tsDir, nil
|
||||
}
|
||||
|
||||
// writePayloadToDir writes the given payload to the given directory. The
|
||||
// directory must exist.
|
||||
func (w *AtomicWriter) writePayloadToDir(payload map[string]FileProjection, dir string) error {
|
||||
for userVisiblePath, fileProjection := range payload {
|
||||
content := fileProjection.Data
|
||||
mode := os.FileMode(fileProjection.Mode)
|
||||
fullPath := filepath.Join(dir, userVisiblePath)
|
||||
baseDir, _ := filepath.Split(fullPath)
|
||||
|
||||
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
|
||||
klog.Errorf("%s: unable to create directory %s: %v", w.logContext, baseDir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(fullPath, content, mode); err != nil {
|
||||
klog.Errorf("%s: unable to write file %s with mode %v: %v", w.logContext, fullPath, mode, err)
|
||||
return err
|
||||
}
|
||||
// Chmod is needed because ioutil.WriteFile() ends up calling
|
||||
// open(2) to create the file, so the final mode used is "mode &
|
||||
// ~umask". But we want to make sure the specified mode is used
|
||||
// in the file no matter what the umask is.
|
||||
if err := os.Chmod(fullPath, mode); err != nil {
|
||||
klog.Errorf("%s: unable to change file %s with mode %v: %v", w.logContext, fullPath, mode, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if fileProjection.FsUser == nil {
|
||||
continue
|
||||
}
|
||||
if err := os.Chown(fullPath, int(*fileProjection.FsUser), -1); err != nil {
|
||||
klog.Errorf("%s: unable to change file %s with owner %v: %v", w.logContext, fullPath, int(*fileProjection.FsUser), err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createUserVisibleFiles creates the relative symlinks for all the
|
||||
// files configured in the payload. If the directory in a file path does not
|
||||
// exist, it is created.
|
||||
//
|
||||
// Viz:
|
||||
// For files: "bar", "foo/bar", "baz/bar", "foo/baz/blah"
|
||||
// the following symlinks are created:
|
||||
// bar -> ..data/bar
|
||||
// foo -> ..data/foo
|
||||
// baz -> ..data/baz
|
||||
func (w *AtomicWriter) createUserVisibleFiles(payload map[string]FileProjection) error {
|
||||
for userVisiblePath := range payload {
|
||||
slashpos := strings.Index(userVisiblePath, string(os.PathSeparator))
|
||||
if slashpos == -1 {
|
||||
slashpos = len(userVisiblePath)
|
||||
}
|
||||
linkname := userVisiblePath[:slashpos]
|
||||
_, err := os.Readlink(filepath.Join(w.targetDir, linkname))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// The link into the data directory for this path doesn't exist; create it
|
||||
visibleFile := filepath.Join(w.targetDir, linkname)
|
||||
dataDirFile := filepath.Join(dataDirName, linkname)
|
||||
|
||||
err = os.Symlink(dataDirFile, visibleFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeUserVisiblePaths removes the set of paths from the user-visible
|
||||
// portion of the writer's target directory.
|
||||
func (w *AtomicWriter) removeUserVisiblePaths(paths sets.String) error {
|
||||
ps := string(os.PathSeparator)
|
||||
var lasterr error
|
||||
for p := range paths {
|
||||
// only remove symlinks from the volume root directory (i.e. items that don't contain '/')
|
||||
if strings.Contains(p, ps) {
|
||||
continue
|
||||
}
|
||||
if err := os.Remove(filepath.Join(w.targetDir, p)); err != nil {
|
||||
klog.Errorf("%s: error pruning old user-visible path %s: %v", w.logContext, p, err)
|
||||
lasterr = err
|
||||
}
|
||||
}
|
||||
|
||||
return lasterr
|
||||
}
|
70
vendor/k8s.io/kubernetes/pkg/volume/util/attach_limit.go
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/pkg/volume/util/attach_limit.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2018 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 util
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// This file is a common place holder for volume limit utility constants
|
||||
// shared between volume package and scheduler
|
||||
|
||||
const (
|
||||
// EBSVolumeLimitKey resource name that will store volume limits for EBS
|
||||
EBSVolumeLimitKey = "attachable-volumes-aws-ebs"
|
||||
// EBSNitroLimitRegex finds nitro instance types with different limit than EBS defaults
|
||||
EBSNitroLimitRegex = "^[cmr]5.*|t3|z1d"
|
||||
// DefaultMaxEBSVolumes is the limit for volumes attached to an instance.
|
||||
// Amazon recommends no more than 40; the system root volume uses at least one.
|
||||
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/volume_limits.html#linux-specific-volume-limits
|
||||
DefaultMaxEBSVolumes = 39
|
||||
// DefaultMaxEBSNitroVolumeLimit is default EBS volume limit on m5 and c5 instances
|
||||
DefaultMaxEBSNitroVolumeLimit = 25
|
||||
// AzureVolumeLimitKey stores resource name that will store volume limits for Azure
|
||||
AzureVolumeLimitKey = "attachable-volumes-azure-disk"
|
||||
// GCEVolumeLimitKey stores resource name that will store volume limits for GCE node
|
||||
GCEVolumeLimitKey = "attachable-volumes-gce-pd"
|
||||
|
||||
// CinderVolumeLimitKey contains Volume limit key for Cinder
|
||||
CinderVolumeLimitKey = "attachable-volumes-cinder"
|
||||
// DefaultMaxCinderVolumes defines the maximum number of PD Volumes for Cinder
|
||||
// For Openstack we are keeping this to a high enough value so as depending on backend
|
||||
// cluster admins can configure it.
|
||||
DefaultMaxCinderVolumes = 256
|
||||
|
||||
// CSIAttachLimitPrefix defines prefix used for CSI volumes
|
||||
CSIAttachLimitPrefix = "attachable-volumes-csi-"
|
||||
|
||||
// ResourceNameLengthLimit stores maximum allowed Length for a ResourceName
|
||||
ResourceNameLengthLimit = 63
|
||||
)
|
||||
|
||||
// GetCSIAttachLimitKey returns limit key used for CSI volumes
|
||||
func GetCSIAttachLimitKey(driverName string) string {
|
||||
csiPrefixLength := len(CSIAttachLimitPrefix)
|
||||
totalkeyLength := csiPrefixLength + len(driverName)
|
||||
if totalkeyLength >= ResourceNameLengthLimit {
|
||||
charsFromDriverName := driverName[:23]
|
||||
hash := sha1.New()
|
||||
hash.Write([]byte(driverName))
|
||||
hashed := hex.EncodeToString(hash.Sum(nil))
|
||||
hashed = hashed[:16]
|
||||
return CSIAttachLimitPrefix + charsFromDriverName + hashed
|
||||
}
|
||||
return CSIAttachLimitPrefix + driverName
|
||||
}
|
34
vendor/k8s.io/kubernetes/pkg/volume/util/device_util.go
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/pkg/volume/util/device_util.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2016 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 util
|
||||
|
||||
// DeviceUtil is a util for common device methods
|
||||
type DeviceUtil interface {
|
||||
FindMultipathDeviceForDevice(disk string) string
|
||||
FindSlaveDevicesOnMultipath(disk string) []string
|
||||
GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error)
|
||||
FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error)
|
||||
}
|
||||
|
||||
type deviceHandler struct {
|
||||
getIo IoUtil
|
||||
}
|
||||
|
||||
// NewDeviceHandler Create a new IoHandler implementation
|
||||
func NewDeviceHandler(io IoUtil) DeviceUtil {
|
||||
return &deviceHandler{getIo: io}
|
||||
}
|
301
vendor/k8s.io/kubernetes/pkg/volume/util/device_util_linux.go
generated
vendored
Normal file
301
vendor/k8s.io/kubernetes/pkg/volume/util/device_util_linux.go
generated
vendored
Normal file
@ -0,0 +1,301 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2016 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 util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// FindMultipathDeviceForDevice given a device name like /dev/sdx, find the devicemapper parent
|
||||
func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string {
|
||||
io := handler.getIo
|
||||
disk, err := findDeviceForPath(device, io)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
sysPath := "/sys/block/"
|
||||
if dirs, err := io.ReadDir(sysPath); err == nil {
|
||||
for _, f := range dirs {
|
||||
name := f.Name()
|
||||
if strings.HasPrefix(name, "dm-") {
|
||||
if _, err1 := io.Lstat(sysPath + name + "/slaves/" + disk); err1 == nil {
|
||||
return "/dev/" + name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findDeviceForPath Find the underlaying disk for a linked path such as /dev/disk/by-path/XXXX or /dev/mapper/XXXX
|
||||
// will return sdX or hdX etc, if /dev/sdX is passed in then sdX will be returned
|
||||
func findDeviceForPath(path string, io IoUtil) (string, error) {
|
||||
devicePath, err := io.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// if path /dev/hdX split into "", "dev", "hdX" then we will
|
||||
// return just the last part
|
||||
parts := strings.Split(devicePath, "/")
|
||||
if len(parts) == 3 && strings.HasPrefix(parts[1], "dev") {
|
||||
return parts[2], nil
|
||||
}
|
||||
return "", errors.New("Illegal path for device " + devicePath)
|
||||
}
|
||||
|
||||
// FindSlaveDevicesOnMultipath given a dm name like /dev/dm-1, find all devices
|
||||
// which are managed by the devicemapper dm-1.
|
||||
func (handler *deviceHandler) FindSlaveDevicesOnMultipath(dm string) []string {
|
||||
var devices []string
|
||||
io := handler.getIo
|
||||
// Split path /dev/dm-1 into "", "dev", "dm-1"
|
||||
parts := strings.Split(dm, "/")
|
||||
if len(parts) != 3 || !strings.HasPrefix(parts[1], "dev") {
|
||||
return devices
|
||||
}
|
||||
disk := parts[2]
|
||||
slavesPath := filepath.Join("/sys/block/", disk, "/slaves/")
|
||||
if files, err := io.ReadDir(slavesPath); err == nil {
|
||||
for _, f := range files {
|
||||
devices = append(devices, filepath.Join("/dev/", f.Name()))
|
||||
}
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
// GetISCSIPortalHostMapForTarget given a target iqn, find all the scsi hosts logged into
|
||||
// that target. Returns a map of iSCSI portals (string) to SCSI host numbers (integers).
|
||||
//
|
||||
// For example: {
|
||||
// "192.168.30.7:3260": 2,
|
||||
// "192.168.30.8:3260": 3,
|
||||
// }
|
||||
func (handler *deviceHandler) GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error) {
|
||||
portalHostMap := make(map[string]int)
|
||||
io := handler.getIo
|
||||
|
||||
// Iterate over all the iSCSI hosts in sysfs
|
||||
sysPath := "/sys/class/iscsi_host"
|
||||
hostDirs, err := io.ReadDir(sysPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return portalHostMap, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
for _, hostDir := range hostDirs {
|
||||
// iSCSI hosts are always of the format "host%d"
|
||||
// See drivers/scsi/hosts.c in Linux
|
||||
hostName := hostDir.Name()
|
||||
if !strings.HasPrefix(hostName, "host") {
|
||||
continue
|
||||
}
|
||||
hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
|
||||
if err != nil {
|
||||
klog.Errorf("Could not get number from iSCSI host: %s", hostName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate over the children of the iscsi_host device
|
||||
// We are looking for the associated session
|
||||
devicePath := sysPath + "/" + hostName + "/device"
|
||||
deviceDirs, err := io.ReadDir(devicePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, deviceDir := range deviceDirs {
|
||||
// Skip over files that aren't the session
|
||||
// Sessions are of the format "session%u"
|
||||
// See drivers/scsi/scsi_transport_iscsi.c in Linux
|
||||
sessionName := deviceDir.Name()
|
||||
if !strings.HasPrefix(sessionName, "session") {
|
||||
continue
|
||||
}
|
||||
|
||||
sessionPath := devicePath + "/" + sessionName
|
||||
|
||||
// Read the target name for the iSCSI session
|
||||
targetNamePath := sessionPath + "/iscsi_session/" + sessionName + "/targetname"
|
||||
targetName, err := io.ReadFile(targetNamePath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore hosts that don't matchthe target we were looking for.
|
||||
if strings.TrimSpace(string(targetName)) != targetIqn {
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate over the children of the iSCSI session looking
|
||||
// for the iSCSI connection.
|
||||
dirs2, err := io.ReadDir(sessionPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err)
|
||||
continue
|
||||
}
|
||||
for _, dir2 := range dirs2 {
|
||||
// Skip over files that aren't the connection
|
||||
// Connections are of the format "connection%d:%u"
|
||||
// See drivers/scsi/scsi_transport_iscsi.c in Linux
|
||||
dirName := dir2.Name()
|
||||
if !strings.HasPrefix(dirName, "connection") {
|
||||
continue
|
||||
}
|
||||
|
||||
connectionPath := sessionPath + "/" + dirName + "/iscsi_connection/" + dirName
|
||||
|
||||
// Read the current and persistent portal information for the connection.
|
||||
addrPath := connectionPath + "/address"
|
||||
addr, err := io.ReadFile(addrPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
portPath := connectionPath + "/port"
|
||||
port, err := io.ReadFile(portPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
persistentAddrPath := connectionPath + "/persistent_address"
|
||||
persistentAddr, err := io.ReadFile(persistentAddrPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
persistentPortPath := connectionPath + "/persistent_port"
|
||||
persistentPort, err := io.ReadFile(persistentPortPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add entries to the map for both the current and persistent portals
|
||||
// pointing to the SCSI host for those connections
|
||||
// JoinHostPort will add `[]` around IPv6 addresses.
|
||||
portal := net.JoinHostPort(strings.TrimSpace(string(addr)), strings.TrimSpace(string(port)))
|
||||
portalHostMap[portal] = hostNumber
|
||||
|
||||
persistentPortal := net.JoinHostPort(strings.TrimSpace(string(persistentAddr)), strings.TrimSpace(string(persistentPort)))
|
||||
portalHostMap[persistentPortal] = hostNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return portalHostMap, nil
|
||||
}
|
||||
|
||||
// FindDevicesForISCSILun given an iqn, and lun number, find all the devices
|
||||
// corresponding to that LUN.
|
||||
func (handler *deviceHandler) FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error) {
|
||||
devices := make([]string, 0)
|
||||
io := handler.getIo
|
||||
|
||||
// Iterate over all the iSCSI hosts in sysfs
|
||||
sysPath := "/sys/class/iscsi_host"
|
||||
hostDirs, err := io.ReadDir(sysPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, hostDir := range hostDirs {
|
||||
// iSCSI hosts are always of the format "host%d"
|
||||
// See drivers/scsi/hosts.c in Linux
|
||||
hostName := hostDir.Name()
|
||||
if !strings.HasPrefix(hostName, "host") {
|
||||
continue
|
||||
}
|
||||
hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
|
||||
if err != nil {
|
||||
klog.Errorf("Could not get number from iSCSI host: %s", hostName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate over the children of the iscsi_host device
|
||||
// We are looking for the associated session
|
||||
devicePath := sysPath + "/" + hostName + "/device"
|
||||
deviceDirs, err := io.ReadDir(devicePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, deviceDir := range deviceDirs {
|
||||
// Skip over files that aren't the session
|
||||
// Sessions are of the format "session%u"
|
||||
// See drivers/scsi/scsi_transport_iscsi.c in Linux
|
||||
sessionName := deviceDir.Name()
|
||||
if !strings.HasPrefix(sessionName, "session") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the target name for the iSCSI session
|
||||
targetNamePath := devicePath + "/" + sessionName + "/iscsi_session/" + sessionName + "/targetname"
|
||||
targetName, err := io.ReadFile(targetNamePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only if the session matches the target we were looking for,
|
||||
// add it to the map
|
||||
if strings.TrimSpace(string(targetName)) != targetIqn {
|
||||
continue
|
||||
}
|
||||
|
||||
// The list of block devices on the scsi bus will be in a
|
||||
// directory called "target%d:%d:%d".
|
||||
// See drivers/scsi/scsi_scan.c in Linux
|
||||
// We assume the channel/bus and device/controller are always zero for iSCSI
|
||||
targetPath := devicePath + "/" + sessionName + fmt.Sprintf("/target%d:0:0", hostNumber)
|
||||
|
||||
// The block device for a given lun will be "%d:%d:%d:%d" --
|
||||
// host:channel:bus:LUN
|
||||
blockDevicePath := targetPath + fmt.Sprintf("/%d:0:0:%d", hostNumber, lun)
|
||||
|
||||
// If the LUN doesn't exist on this bus, continue on
|
||||
_, err = io.Lstat(blockDevicePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the block directory, there should only be one child --
|
||||
// the block device "sd*"
|
||||
path := blockDevicePath + "/block"
|
||||
dirs, err := io.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if 0 < len(dirs) {
|
||||
devices = append(devices, dirs[0].Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
43
vendor/k8s.io/kubernetes/pkg/volume/util/device_util_unsupported.go
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/pkg/volume/util/device_util_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2016 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 util
|
||||
|
||||
// FindMultipathDeviceForDevice unsupported returns ""
|
||||
func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// FindSlaveDevicesOnMultipath unsupported returns ""
|
||||
func (handler *deviceHandler) FindSlaveDevicesOnMultipath(disk string) []string {
|
||||
out := []string{}
|
||||
return out
|
||||
}
|
||||
|
||||
// GetISCSIPortalHostMapForTarget unsupported returns nil
|
||||
func (handler *deviceHandler) GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error) {
|
||||
portalHostMap := make(map[string]int)
|
||||
return portalHostMap, nil
|
||||
}
|
||||
|
||||
// FindDevicesForISCSILun unsupported returns nil
|
||||
func (handler *deviceHandler) FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error) {
|
||||
devices := []string{}
|
||||
return devices, nil
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/volume/util/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/volume/util/doc.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2015 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 util contains utility code for use by volume plugins.
|
||||
package util // import "k8s.io/kubernetes/pkg/volume/util"
|
25
vendor/k8s.io/kubernetes/pkg/volume/util/finalizer.go
generated
vendored
Normal file
25
vendor/k8s.io/kubernetes/pkg/volume/util/finalizer.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright 2017 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 util
|
||||
|
||||
const (
|
||||
// PVCProtectionFinalizer is the name of finalizer on PVCs that have a running pod.
|
||||
PVCProtectionFinalizer = "kubernetes.io/pvc-protection"
|
||||
|
||||
// PVProtectionFinalizer is the name of finalizer on PVs that are bound by PVCs
|
||||
PVProtectionFinalizer = "kubernetes.io/pv-protection"
|
||||
)
|
2
vendor/k8s.io/kubernetes/pkg/volume/util/hostutil/hostutil_unsupported.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/volume/util/hostutil/hostutil_unsupported.go
generated
vendored
@ -93,7 +93,7 @@ func (hu *HostUtil) GetSELinuxSupport(pathname string) (bool, error) {
|
||||
return false, errUnsupported
|
||||
}
|
||||
|
||||
//GetMode always returns an error on unsupported platforms
|
||||
// GetMode always returns an error on unsupported platforms
|
||||
func (hu *HostUtil) GetMode(pathname string) (os.FileMode, error) {
|
||||
return 0, errUnsupported
|
||||
}
|
||||
|
51
vendor/k8s.io/kubernetes/pkg/volume/util/io_util.go
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/pkg/volume/util/io_util.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2016 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 util
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// IoUtil is a mockable util for common IO operations
|
||||
type IoUtil interface {
|
||||
ReadFile(filename string) ([]byte, error)
|
||||
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||
Lstat(name string) (os.FileInfo, error)
|
||||
EvalSymlinks(path string) (string, error)
|
||||
}
|
||||
|
||||
type osIOHandler struct{}
|
||||
|
||||
// NewIOHandler Create a new IoHandler implementation
|
||||
func NewIOHandler() IoUtil {
|
||||
return &osIOHandler{}
|
||||
}
|
||||
|
||||
func (handler *osIOHandler) ReadFile(filename string) ([]byte, error) {
|
||||
return ioutil.ReadFile(filename)
|
||||
}
|
||||
func (handler *osIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
return ioutil.ReadDir(dirname)
|
||||
}
|
||||
func (handler *osIOHandler) Lstat(name string) (os.FileInfo, error) {
|
||||
return os.Lstat(name)
|
||||
}
|
||||
func (handler *osIOHandler) EvalSymlinks(path string) (string, error) {
|
||||
return filepath.EvalSymlinks(path)
|
||||
}
|
160
vendor/k8s.io/kubernetes/pkg/volume/util/metrics.go
generated
vendored
Normal file
160
vendor/k8s.io/kubernetes/pkg/volume/util/metrics.go
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright 2017 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||
)
|
||||
|
||||
const (
|
||||
statusSuccess = "success"
|
||||
statusFailUnknown = "fail-unknown"
|
||||
)
|
||||
|
||||
/*
|
||||
* By default, all the following metrics are defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
*
|
||||
* Promoting the stability level of the metric is a responsibility of the component owner, since it
|
||||
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
|
||||
* the metric stability policy.
|
||||
*/
|
||||
var storageOperationMetric = metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Name: "storage_operation_duration_seconds",
|
||||
Help: "Storage operation duration",
|
||||
Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 15, 25, 50, 120, 300, 600},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"volume_plugin", "operation_name", "status", "migrated"},
|
||||
)
|
||||
|
||||
var storageOperationEndToEndLatencyMetric = metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Name: "volume_operation_total_seconds",
|
||||
Help: "Storage operation end to end duration in seconds",
|
||||
Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 15, 25, 50, 120, 300, 600},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"plugin_name", "operation_name"},
|
||||
)
|
||||
|
||||
var csiOperationsLatencyMetric = metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Subsystem: "csi",
|
||||
Name: "operations_seconds",
|
||||
Help: "Container Storage Interface operation duration with gRPC error code status total",
|
||||
Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 15, 25, 50, 120, 300, 600},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"driver_name", "method_name", "grpc_status_code", "migrated"},
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMetrics()
|
||||
}
|
||||
|
||||
func registerMetrics() {
|
||||
// legacyregistry is the internal k8s wrapper around the prometheus
|
||||
// global registry, used specifically for metric stability enforcement
|
||||
legacyregistry.MustRegister(storageOperationMetric)
|
||||
legacyregistry.MustRegister(storageOperationEndToEndLatencyMetric)
|
||||
legacyregistry.MustRegister(csiOperationsLatencyMetric)
|
||||
}
|
||||
|
||||
// OperationCompleteHook returns a hook to call when an operation is completed
|
||||
func OperationCompleteHook(plugin, operationName string) func(types.CompleteFuncParam) {
|
||||
requestTime := time.Now()
|
||||
opComplete := func(c types.CompleteFuncParam) {
|
||||
timeTaken := time.Since(requestTime).Seconds()
|
||||
// Create metric with operation name and plugin name
|
||||
status := statusSuccess
|
||||
if *c.Err != nil {
|
||||
// TODO: Establish well-known error codes to be able to distinguish
|
||||
// user configuration errors from system errors.
|
||||
status = statusFailUnknown
|
||||
}
|
||||
migrated := false
|
||||
if c.Migrated != nil {
|
||||
migrated = *c.Migrated
|
||||
}
|
||||
storageOperationMetric.WithLabelValues(plugin, operationName, status, strconv.FormatBool(migrated)).Observe(timeTaken)
|
||||
}
|
||||
return opComplete
|
||||
}
|
||||
|
||||
// FSGroupCompleteHook returns a hook to call when volume recursive permission is changed
|
||||
func FSGroupCompleteHook(plugin volume.VolumePlugin, spec *volume.Spec) func(types.CompleteFuncParam) {
|
||||
return OperationCompleteHook(GetFullQualifiedPluginNameForVolume(plugin.GetPluginName(), spec), "volume_apply_access_control")
|
||||
}
|
||||
|
||||
// GetFullQualifiedPluginNameForVolume returns full qualified plugin name for
|
||||
// given volume. For CSI plugin, it appends plugin driver name at the end of
|
||||
// plugin name, e.g. kubernetes.io/csi:csi-hostpath. It helps to distinguish
|
||||
// between metrics emitted for CSI volumes which may be handled by different
|
||||
// CSI plugin drivers.
|
||||
func GetFullQualifiedPluginNameForVolume(pluginName string, spec *volume.Spec) string {
|
||||
if spec != nil {
|
||||
if spec.Volume != nil && spec.Volume.CSI != nil {
|
||||
return fmt.Sprintf("%s:%s", pluginName, spec.Volume.CSI.Driver)
|
||||
}
|
||||
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CSI != nil {
|
||||
return fmt.Sprintf("%s:%s", pluginName, spec.PersistentVolume.Spec.CSI.Driver)
|
||||
}
|
||||
}
|
||||
return pluginName
|
||||
}
|
||||
|
||||
// RecordOperationLatencyMetric records the end to end latency for certain operation
|
||||
// into metric volume_operation_total_seconds
|
||||
func RecordOperationLatencyMetric(plugin, operationName string, secondsTaken float64) {
|
||||
storageOperationEndToEndLatencyMetric.WithLabelValues(plugin, operationName).Observe(secondsTaken)
|
||||
}
|
||||
|
||||
// RecordCSIOperationLatencyMetrics records the CSI operation latency and grpc status
|
||||
// into metric csi_kubelet_operations_seconds
|
||||
func RecordCSIOperationLatencyMetrics(driverName string,
|
||||
operationName string,
|
||||
operationErr error,
|
||||
operationDuration time.Duration,
|
||||
migrated string) {
|
||||
csiOperationsLatencyMetric.WithLabelValues(driverName, operationName, getErrorCode(operationErr), migrated).Observe(operationDuration.Seconds())
|
||||
}
|
||||
|
||||
func getErrorCode(err error) string {
|
||||
if err == nil {
|
||||
return codes.OK.String()
|
||||
}
|
||||
|
||||
st, ok := status.FromError(err)
|
||||
if !ok {
|
||||
// This is not gRPC error. The operation must have failed before gRPC
|
||||
// method was called, otherwise we would get gRPC error.
|
||||
return "unknown-non-grpc"
|
||||
}
|
||||
|
||||
return st.Code().String()
|
||||
}
|
100
vendor/k8s.io/kubernetes/pkg/volume/util/nested_volumes.go
generated
vendored
Normal file
100
vendor/k8s.io/kubernetes/pkg/volume/util/nested_volumes.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2018 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
)
|
||||
|
||||
// getNestedMountpoints returns a list of mountpoint directories that should be created
|
||||
// for the volume indicated by name.
|
||||
// note: the returned list is relative to baseDir
|
||||
func getNestedMountpoints(name, baseDir string, pod v1.Pod) ([]string, error) {
|
||||
var retval []string
|
||||
checkContainer := func(container *v1.Container) error {
|
||||
var allMountPoints []string // all mount points in this container
|
||||
var myMountPoints []string // mount points that match name
|
||||
for _, vol := range container.VolumeMounts {
|
||||
cleaned := filepath.Clean(vol.MountPath)
|
||||
allMountPoints = append(allMountPoints, cleaned)
|
||||
if vol.Name == name {
|
||||
myMountPoints = append(myMountPoints, cleaned)
|
||||
}
|
||||
}
|
||||
sort.Strings(allMountPoints)
|
||||
parentPrefix := ".." + string(os.PathSeparator)
|
||||
// Examine each place where this volume is mounted
|
||||
for _, myMountPoint := range myMountPoints {
|
||||
if strings.HasPrefix(myMountPoint, parentPrefix) {
|
||||
// Don't let a container trick us into creating directories outside of its rootfs
|
||||
return fmt.Errorf("invalid container mount point %v", myMountPoint)
|
||||
}
|
||||
myMPSlash := myMountPoint + string(os.PathSeparator)
|
||||
// The previously found nested mountpoint (or "" if none found yet)
|
||||
prevNestedMP := ""
|
||||
// examine each mount point to see if it's nested beneath this volume
|
||||
// (but skip any that are double-nested beneath this volume)
|
||||
// For example, if this volume is mounted as /dir and other volumes are mounted
|
||||
// as /dir/nested and /dir/nested/other, only create /dir/nested.
|
||||
for _, mp := range allMountPoints {
|
||||
if !strings.HasPrefix(mp, myMPSlash) {
|
||||
continue // skip -- not nested beneath myMountPoint
|
||||
}
|
||||
if prevNestedMP != "" && strings.HasPrefix(mp, prevNestedMP) {
|
||||
continue // skip -- double nested beneath myMountPoint
|
||||
}
|
||||
// since this mount point is nested, remember it so that we can check that following ones aren't nested beneath this one
|
||||
prevNestedMP = mp + string(os.PathSeparator)
|
||||
retval = append(retval, mp[len(myMPSlash):])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var retErr error
|
||||
podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
|
||||
retErr = checkContainer(c)
|
||||
return retErr == nil
|
||||
})
|
||||
if retErr != nil {
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
// MakeNestedMountpoints creates mount points in baseDir for volumes mounted beneath name
|
||||
func MakeNestedMountpoints(name, baseDir string, pod v1.Pod) error {
|
||||
dirs, err := getNestedMountpoints(name, baseDir, pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
err := os.MkdirAll(filepath.Join(baseDir, dir), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create nested volume mountpoints: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
6
vendor/k8s.io/kubernetes/pkg/volume/util/recyclerclient/recycler_client.go
generated
vendored
6
vendor/k8s.io/kubernetes/pkg/volume/util/recyclerclient/recycler_client.go
generated
vendored
@ -43,9 +43,9 @@ type RecycleEventRecorder func(eventtype, message string)
|
||||
// function deletes it as it is not able to judge if it is an old recycler
|
||||
// or user has forged a fake recycler to block Kubernetes from recycling.//
|
||||
//
|
||||
// pod - the pod designed by a volume plugin to recycle the volume. pod.Name
|
||||
// will be overwritten with unique name based on PV.Name.
|
||||
// client - kube client for API operations.
|
||||
// pod - the pod designed by a volume plugin to recycle the volume. pod.Name
|
||||
// will be overwritten with unique name based on PV.Name.
|
||||
// client - kube client for API operations.
|
||||
func RecycleVolumeByWatchingPodUntilCompletion(pvName string, pod *v1.Pod, kubeClient clientset.Interface, recorder RecycleEventRecorder) error {
|
||||
return internalRecycleVolumeByWatchingPodUntilCompletion(pvName, pod, newRecyclerClient(kubeClient, recorder))
|
||||
}
|
||||
|
394
vendor/k8s.io/kubernetes/pkg/volume/util/resize_util.go
generated
vendored
Normal file
394
vendor/k8s.io/kubernetes/pkg/volume/util/resize_util.go
generated
vendored
Normal file
@ -0,0 +1,394 @@
|
||||
/*
|
||||
Copyright 2018 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 util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
|
||||
"k8s.io/mount-utils"
|
||||
)
|
||||
|
||||
var (
|
||||
knownResizeConditions = map[v1.PersistentVolumeClaimConditionType]bool{
|
||||
v1.PersistentVolumeClaimFileSystemResizePending: true,
|
||||
v1.PersistentVolumeClaimResizing: true,
|
||||
}
|
||||
|
||||
// AnnPreResizeCapacity annotation is added to a PV when expanding volume.
|
||||
// Its value is status capacity of the PVC prior to the volume expansion
|
||||
// Its value will be set by the external-resizer when it deems that filesystem resize is required after resizing volume.
|
||||
// Its value will be used by pv_controller to determine pvc's status capacity when binding pvc and pv.
|
||||
AnnPreResizeCapacity = "volume.alpha.kubernetes.io/pre-resize-capacity"
|
||||
)
|
||||
|
||||
type resizeProcessStatus struct {
|
||||
condition v1.PersistentVolumeClaimCondition
|
||||
processed bool
|
||||
}
|
||||
|
||||
// ClaimToClaimKey return namespace/name string for pvc
|
||||
func ClaimToClaimKey(claim *v1.PersistentVolumeClaim) string {
|
||||
return fmt.Sprintf("%s/%s", claim.Namespace, claim.Name)
|
||||
}
|
||||
|
||||
// UpdatePVSize updates just pv size after cloudprovider resizing is successful
|
||||
func UpdatePVSize(
|
||||
pv *v1.PersistentVolume,
|
||||
newSize resource.Quantity,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolume, error) {
|
||||
pvClone := pv.DeepCopy()
|
||||
pvClone.Spec.Capacity[v1.ResourceStorage] = newSize
|
||||
|
||||
return PatchPV(pv, pvClone, kubeClient)
|
||||
}
|
||||
|
||||
// AddAnnPreResizeCapacity adds volume.alpha.kubernetes.io/pre-resize-capacity from the pv
|
||||
func AddAnnPreResizeCapacity(
|
||||
pv *v1.PersistentVolume,
|
||||
oldCapacity resource.Quantity,
|
||||
kubeClient clientset.Interface) error {
|
||||
// if the pv already has a resize annotation skip the process
|
||||
if metav1.HasAnnotation(pv.ObjectMeta, AnnPreResizeCapacity) {
|
||||
return nil
|
||||
}
|
||||
|
||||
pvClone := pv.DeepCopy()
|
||||
if pvClone.ObjectMeta.Annotations == nil {
|
||||
pvClone.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
pvClone.ObjectMeta.Annotations[AnnPreResizeCapacity] = oldCapacity.String()
|
||||
|
||||
_, err := PatchPV(pv, pvClone, kubeClient)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAnnPreResizeCapacity deletes volume.alpha.kubernetes.io/pre-resize-capacity from the pv
|
||||
func DeleteAnnPreResizeCapacity(
|
||||
pv *v1.PersistentVolume,
|
||||
kubeClient clientset.Interface) error {
|
||||
// if the pv does not have a resize annotation skip the entire process
|
||||
if !metav1.HasAnnotation(pv.ObjectMeta, AnnPreResizeCapacity) {
|
||||
return nil
|
||||
}
|
||||
pvClone := pv.DeepCopy()
|
||||
delete(pvClone.ObjectMeta.Annotations, AnnPreResizeCapacity)
|
||||
_, err := PatchPV(pv, pvClone, kubeClient)
|
||||
return err
|
||||
}
|
||||
|
||||
// PatchPV creates and executes a patch for pv
|
||||
func PatchPV(
|
||||
oldPV *v1.PersistentVolume,
|
||||
newPV *v1.PersistentVolume,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolume, error) {
|
||||
oldData, err := json.Marshal(oldPV)
|
||||
if err != nil {
|
||||
return oldPV, fmt.Errorf("unexpected error marshaling old PV %q with error : %v", oldPV.Name, err)
|
||||
}
|
||||
|
||||
newData, err := json.Marshal(newPV)
|
||||
if err != nil {
|
||||
return oldPV, fmt.Errorf("unexpected error marshaling new PV %q with error : %v", newPV.Name, err)
|
||||
}
|
||||
|
||||
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, oldPV)
|
||||
if err != nil {
|
||||
return oldPV, fmt.Errorf("error Creating two way merge patch for PV %q with error : %v", oldPV.Name, err)
|
||||
}
|
||||
|
||||
updatedPV, err := kubeClient.CoreV1().PersistentVolumes().Patch(context.TODO(), oldPV.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
return oldPV, fmt.Errorf("error Patching PV %q with error : %v", oldPV.Name, err)
|
||||
}
|
||||
return updatedPV, nil
|
||||
}
|
||||
|
||||
// MarkResizeInProgressWithResizer marks cloudprovider resizing as in progress
|
||||
// and also annotates the PVC with the name of the resizer.
|
||||
func MarkResizeInProgressWithResizer(
|
||||
pvc *v1.PersistentVolumeClaim,
|
||||
resizerName string,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
// Mark PVC as Resize Started
|
||||
progressCondition := v1.PersistentVolumeClaimCondition{
|
||||
Type: v1.PersistentVolumeClaimResizing,
|
||||
Status: v1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
}
|
||||
conditions := []v1.PersistentVolumeClaimCondition{progressCondition}
|
||||
newPVC := pvc.DeepCopy()
|
||||
newPVC = MergeResizeConditionOnPVC(newPVC, conditions)
|
||||
newPVC = setResizer(newPVC, resizerName)
|
||||
return PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||
}
|
||||
|
||||
func MarkControllerReisizeInProgress(pvc *v1.PersistentVolumeClaim, resizerName string, newSize resource.Quantity, kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
// Mark PVC as Resize Started
|
||||
progressCondition := v1.PersistentVolumeClaimCondition{
|
||||
Type: v1.PersistentVolumeClaimResizing,
|
||||
Status: v1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
}
|
||||
controllerExpansionInProgress := v1.PersistentVolumeClaimControllerExpansionInProgress
|
||||
conditions := []v1.PersistentVolumeClaimCondition{progressCondition}
|
||||
newPVC := pvc.DeepCopy()
|
||||
newPVC = MergeResizeConditionOnPVC(newPVC, conditions)
|
||||
newPVC.Status.ResizeStatus = &controllerExpansionInProgress
|
||||
newPVC.Status.AllocatedResources = v1.ResourceList{v1.ResourceStorage: newSize}
|
||||
newPVC = setResizer(newPVC, resizerName)
|
||||
return PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||
}
|
||||
|
||||
// SetClaimResizer sets resizer annotation on PVC
|
||||
func SetClaimResizer(
|
||||
pvc *v1.PersistentVolumeClaim,
|
||||
resizerName string,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
newPVC := pvc.DeepCopy()
|
||||
newPVC = setResizer(newPVC, resizerName)
|
||||
return PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||
}
|
||||
|
||||
func setResizer(pvc *v1.PersistentVolumeClaim, resizerName string) *v1.PersistentVolumeClaim {
|
||||
if val, ok := pvc.Annotations[volumetypes.VolumeResizerKey]; ok && val == resizerName {
|
||||
return pvc
|
||||
}
|
||||
metav1.SetMetaDataAnnotation(&pvc.ObjectMeta, volumetypes.VolumeResizerKey, resizerName)
|
||||
return pvc
|
||||
}
|
||||
|
||||
// MarkForFSResize marks file system resizing as pending
|
||||
func MarkForFSResize(
|
||||
pvc *v1.PersistentVolumeClaim,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
pvcCondition := v1.PersistentVolumeClaimCondition{
|
||||
Type: v1.PersistentVolumeClaimFileSystemResizePending,
|
||||
Status: v1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Message: "Waiting for user to (re-)start a pod to finish file system resize of volume on node.",
|
||||
}
|
||||
conditions := []v1.PersistentVolumeClaimCondition{pvcCondition}
|
||||
newPVC := pvc.DeepCopy()
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure) {
|
||||
expansionPendingOnNode := v1.PersistentVolumeClaimNodeExpansionPending
|
||||
newPVC.Status.ResizeStatus = &expansionPendingOnNode
|
||||
}
|
||||
newPVC = MergeResizeConditionOnPVC(newPVC, conditions)
|
||||
updatedPVC, err := PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||
return updatedPVC, err
|
||||
}
|
||||
|
||||
// MarkResizeFinished marks all resizing as done
|
||||
func MarkResizeFinished(
|
||||
pvc *v1.PersistentVolumeClaim,
|
||||
newSize resource.Quantity,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
return MarkFSResizeFinished(pvc, newSize, kubeClient)
|
||||
}
|
||||
|
||||
// MarkFSResizeFinished marks file system resizing as done
|
||||
func MarkFSResizeFinished(
|
||||
pvc *v1.PersistentVolumeClaim,
|
||||
newSize resource.Quantity,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
newPVC := pvc.DeepCopy()
|
||||
|
||||
newPVC.Status.Capacity[v1.ResourceStorage] = newSize
|
||||
|
||||
// if RecoverVolumeExpansionFailure is enabled, we need to reset ResizeStatus back to nil
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure) {
|
||||
expansionFinished := v1.PersistentVolumeClaimNoExpansionInProgress
|
||||
newPVC.Status.ResizeStatus = &expansionFinished
|
||||
}
|
||||
|
||||
newPVC = MergeResizeConditionOnPVC(newPVC, []v1.PersistentVolumeClaimCondition{})
|
||||
updatedPVC, err := PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||
return updatedPVC, err
|
||||
}
|
||||
|
||||
func MarkControllerExpansionFailed(pvc *v1.PersistentVolumeClaim, kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
expansionFailedOnController := v1.PersistentVolumeClaimControllerExpansionFailed
|
||||
newPVC := pvc.DeepCopy()
|
||||
newPVC.Status.ResizeStatus = &expansionFailedOnController
|
||||
patchBytes, err := createPVCPatch(pvc, newPVC, false /* addResourceVersionCheck */)
|
||||
if err != nil {
|
||||
return pvc, fmt.Errorf("patchPVCStatus failed to patch PVC %q: %v", pvc.Name, err)
|
||||
}
|
||||
|
||||
updatedClaim, updateErr := kubeClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).
|
||||
Patch(context.TODO(), pvc.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}, "status")
|
||||
if updateErr != nil {
|
||||
return pvc, fmt.Errorf("patchPVCStatus failed to patch PVC %q: %v", pvc.Name, updateErr)
|
||||
}
|
||||
return updatedClaim, nil
|
||||
}
|
||||
|
||||
// MarkNodeExpansionFailed marks a PVC for node expansion as failed. Kubelet should not retry expansion
|
||||
// of volumes which are in failed state.
|
||||
func MarkNodeExpansionFailed(pvc *v1.PersistentVolumeClaim, kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
expansionFailedOnNode := v1.PersistentVolumeClaimNodeExpansionFailed
|
||||
newPVC := pvc.DeepCopy()
|
||||
newPVC.Status.ResizeStatus = &expansionFailedOnNode
|
||||
patchBytes, err := createPVCPatch(pvc, newPVC, false /* addResourceVersionCheck */)
|
||||
if err != nil {
|
||||
return pvc, fmt.Errorf("patchPVCStatus failed to patch PVC %q: %v", pvc.Name, err)
|
||||
}
|
||||
|
||||
updatedClaim, updateErr := kubeClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).
|
||||
Patch(context.TODO(), pvc.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}, "status")
|
||||
if updateErr != nil {
|
||||
return pvc, fmt.Errorf("patchPVCStatus failed to patch PVC %q: %v", pvc.Name, updateErr)
|
||||
}
|
||||
return updatedClaim, nil
|
||||
}
|
||||
|
||||
// MarkNodeExpansionInProgress marks pvc expansion in progress on node
|
||||
func MarkNodeExpansionInProgress(pvc *v1.PersistentVolumeClaim, kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
nodeExpansionInProgress := v1.PersistentVolumeClaimNodeExpansionInProgress
|
||||
newPVC := pvc.DeepCopy()
|
||||
newPVC.Status.ResizeStatus = &nodeExpansionInProgress
|
||||
updatedPVC, err := PatchPVCStatus(pvc /* oldPVC */, newPVC, kubeClient)
|
||||
return updatedPVC, err
|
||||
}
|
||||
|
||||
// PatchPVCStatus updates PVC status using PATCH verb
|
||||
// Don't use Update because this can be called from kubelet and if kubelet has an older client its
|
||||
// Updates will overwrite new fields. And to avoid writing to a stale object, add ResourceVersion
|
||||
// to the patch so that Patch will fail if the patch's RV != actual up-to-date RV like Update would
|
||||
func PatchPVCStatus(
|
||||
oldPVC *v1.PersistentVolumeClaim,
|
||||
newPVC *v1.PersistentVolumeClaim,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
patchBytes, err := createPVCPatch(oldPVC, newPVC, true /* addResourceVersionCheck */)
|
||||
if err != nil {
|
||||
return oldPVC, fmt.Errorf("patchPVCStatus failed to patch PVC %q: %v", oldPVC.Name, err)
|
||||
}
|
||||
|
||||
updatedClaim, updateErr := kubeClient.CoreV1().PersistentVolumeClaims(oldPVC.Namespace).
|
||||
Patch(context.TODO(), oldPVC.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}, "status")
|
||||
if updateErr != nil {
|
||||
return oldPVC, fmt.Errorf("patchPVCStatus failed to patch PVC %q: %v", oldPVC.Name, updateErr)
|
||||
}
|
||||
return updatedClaim, nil
|
||||
}
|
||||
|
||||
func createPVCPatch(
|
||||
oldPVC *v1.PersistentVolumeClaim,
|
||||
newPVC *v1.PersistentVolumeClaim, addResourceVersionCheck bool) ([]byte, error) {
|
||||
oldData, err := json.Marshal(oldPVC)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal old data: %v", err)
|
||||
}
|
||||
|
||||
newData, err := json.Marshal(newPVC)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal new data: %v", err)
|
||||
}
|
||||
|
||||
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, oldPVC)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create 2 way merge patch: %v", err)
|
||||
}
|
||||
|
||||
if addResourceVersionCheck {
|
||||
patchBytes, err = addResourceVersion(patchBytes, oldPVC.ResourceVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add resource version: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return patchBytes, nil
|
||||
}
|
||||
|
||||
func addResourceVersion(patchBytes []byte, resourceVersion string) ([]byte, error) {
|
||||
var patchMap map[string]interface{}
|
||||
err := json.Unmarshal(patchBytes, &patchMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling patch: %v", err)
|
||||
}
|
||||
u := unstructured.Unstructured{Object: patchMap}
|
||||
a, err := meta.Accessor(&u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating accessor: %v", err)
|
||||
}
|
||||
a.SetResourceVersion(resourceVersion)
|
||||
versionBytes, err := json.Marshal(patchMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshalling json patch: %v", err)
|
||||
}
|
||||
return versionBytes, nil
|
||||
}
|
||||
|
||||
// MergeResizeConditionOnPVC updates pvc with requested resize conditions
|
||||
// leaving other conditions untouched.
|
||||
func MergeResizeConditionOnPVC(
|
||||
pvc *v1.PersistentVolumeClaim,
|
||||
resizeConditions []v1.PersistentVolumeClaimCondition) *v1.PersistentVolumeClaim {
|
||||
resizeConditionMap := map[v1.PersistentVolumeClaimConditionType]*resizeProcessStatus{}
|
||||
|
||||
for _, condition := range resizeConditions {
|
||||
resizeConditionMap[condition.Type] = &resizeProcessStatus{condition, false}
|
||||
}
|
||||
|
||||
oldConditions := pvc.Status.Conditions
|
||||
newConditions := []v1.PersistentVolumeClaimCondition{}
|
||||
for _, condition := range oldConditions {
|
||||
// If Condition is of not resize type, we keep it.
|
||||
if _, ok := knownResizeConditions[condition.Type]; !ok {
|
||||
newConditions = append(newConditions, condition)
|
||||
continue
|
||||
}
|
||||
|
||||
if newCondition, ok := resizeConditionMap[condition.Type]; ok {
|
||||
if newCondition.condition.Status != condition.Status {
|
||||
newConditions = append(newConditions, newCondition.condition)
|
||||
} else {
|
||||
newConditions = append(newConditions, condition)
|
||||
}
|
||||
newCondition.processed = true
|
||||
}
|
||||
}
|
||||
|
||||
// append all unprocessed conditions
|
||||
for _, newCondition := range resizeConditionMap {
|
||||
if !newCondition.processed {
|
||||
newConditions = append(newConditions, newCondition.condition)
|
||||
}
|
||||
}
|
||||
pvc.Status.Conditions = newConditions
|
||||
return pvc
|
||||
}
|
||||
|
||||
// GenericResizeFS : call generic filesystem resizer for plugins that don't have any special filesystem resize requirements
|
||||
func GenericResizeFS(host volume.VolumeHost, pluginName, devicePath, deviceMountPath string) (bool, error) {
|
||||
resizer := mount.NewResizeFs(host.GetExec(pluginName))
|
||||
return resizer.Resize(devicePath, deviceMountPath)
|
||||
}
|
198
vendor/k8s.io/kubernetes/pkg/volume/util/selinux.go
generated
vendored
Normal file
198
vendor/k8s.io/kubernetes/pkg/volume/util/selinux.go
generated
vendored
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
// SELinuxLabelTranslator translates v1.SELinuxOptions of a process to SELinux file label.
|
||||
type SELinuxLabelTranslator interface {
|
||||
// SELinuxOptionsToFileLabel returns SELinux file label for given SELinuxOptions
|
||||
// of a container process.
|
||||
// When Role, User or Type are empty, they're read from the system defaults.
|
||||
// It returns "" and no error on platforms that do not have SELinux enabled
|
||||
// or don't support SELinux at all.
|
||||
SELinuxOptionsToFileLabel(opts *v1.SELinuxOptions) (string, error)
|
||||
|
||||
// SELinuxEnabled returns true when the OS has enabled SELinux support.
|
||||
SELinuxEnabled() bool
|
||||
}
|
||||
|
||||
// Real implementation of the interface.
|
||||
// On Linux with SELinux enabled it translates. Otherwise it always returns an empty string and no error.
|
||||
type translator struct{}
|
||||
|
||||
var _ SELinuxLabelTranslator = &translator{}
|
||||
|
||||
// NewSELinuxLabelTranslator returns new SELinuxLabelTranslator for the platform.
|
||||
func NewSELinuxLabelTranslator() SELinuxLabelTranslator {
|
||||
return &translator{}
|
||||
}
|
||||
|
||||
// SELinuxOptionsToFileLabel returns SELinux file label for given SELinuxOptions
|
||||
// of a container process.
|
||||
// When Role, User or Type are empty, they're read from the system defaults.
|
||||
// It returns "" and no error on platforms that do not have SELinux enabled
|
||||
// or don't support SELinux at all.
|
||||
func (l *translator) SELinuxOptionsToFileLabel(opts *v1.SELinuxOptions) (string, error) {
|
||||
if opts == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
args := contextOptions(opts)
|
||||
if len(args) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
processLabel, fileLabel, err := label.InitLabels(args)
|
||||
if err != nil {
|
||||
// In theory, this should be unreachable. InitLabels can fail only when args contain an unknown option,
|
||||
// and all options returned by contextOptions are known.
|
||||
return "", err
|
||||
}
|
||||
// InitLabels() may allocate a new unique SELinux label in kubelet memory. The label is *not* allocated
|
||||
// in the container runtime. Clear it to avoid memory problems.
|
||||
// ReleaseLabel on non-allocated label is NOOP.
|
||||
selinux.ReleaseLabel(processLabel)
|
||||
|
||||
return fileLabel, nil
|
||||
}
|
||||
|
||||
// Convert SELinuxOptions to []string accepted by label.InitLabels
|
||||
func contextOptions(opts *v1.SELinuxOptions) []string {
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
args := make([]string, 0, 3)
|
||||
if opts.User != "" {
|
||||
args = append(args, "user:"+opts.User)
|
||||
}
|
||||
if opts.Role != "" {
|
||||
args = append(args, "role:"+opts.Role)
|
||||
}
|
||||
if opts.Type != "" {
|
||||
args = append(args, "type:"+opts.Type)
|
||||
}
|
||||
if opts.Level != "" {
|
||||
args = append(args, "level:"+opts.Level)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (l *translator) SELinuxEnabled() bool {
|
||||
return selinux.GetEnabled()
|
||||
}
|
||||
|
||||
// Fake implementation of the interface for unit tests.
|
||||
type fakeTranslator struct{}
|
||||
|
||||
var _ SELinuxLabelTranslator = &fakeTranslator{}
|
||||
|
||||
// NewFakeSELinuxLabelTranslator returns a fake translator for unit tests.
|
||||
// It imitates a real translator on platforms that do not have SELinux enabled
|
||||
// or don't support SELinux at all.
|
||||
func NewFakeSELinuxLabelTranslator() SELinuxLabelTranslator {
|
||||
return &fakeTranslator{}
|
||||
}
|
||||
|
||||
// SELinuxOptionsToFileLabel returns SELinux file label for given options.
|
||||
func (l *fakeTranslator) SELinuxOptionsToFileLabel(opts *v1.SELinuxOptions) (string, error) {
|
||||
if opts == nil {
|
||||
return "", nil
|
||||
}
|
||||
// Fill empty values from "system defaults" (taken from Fedora Linux).
|
||||
user := opts.User
|
||||
if user == "" {
|
||||
user = "system_u"
|
||||
}
|
||||
|
||||
role := opts.Role
|
||||
if role == "" {
|
||||
role = "object_r"
|
||||
}
|
||||
|
||||
// opts is context of the *process* to run in a container. Translate
|
||||
// process type "container_t" to file label type "container_file_t".
|
||||
// (The rest of the context is the same for processes and files).
|
||||
fileType := opts.Type
|
||||
if fileType == "" || fileType == "container_t" {
|
||||
fileType = "container_file_t"
|
||||
}
|
||||
|
||||
level := opts.Level
|
||||
if level == "" {
|
||||
// If empty, level is allocated randomly.
|
||||
level = "s0:c998,c999"
|
||||
}
|
||||
|
||||
ctx := fmt.Sprintf("%s:%s:%s:%s", user, role, fileType, level)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (l *fakeTranslator) SELinuxEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SupportsSELinuxContextMount checks if the given volumeSpec supports with mount -o context
|
||||
func SupportsSELinuxContextMount(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) (bool, error) {
|
||||
plugin, _ := volumePluginMgr.FindPluginBySpec(volumeSpec)
|
||||
if plugin != nil {
|
||||
return plugin.SupportsSELinuxContextMount(volumeSpec)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// VolumeSupportsSELinuxMount returns true if given volume access mode can support mount with SELinux mount options.
|
||||
func VolumeSupportsSELinuxMount(volumeSpec *volume.Spec) bool {
|
||||
// Right now, SELinux mount is supported only for ReadWriteOncePod volumes.
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod) {
|
||||
return false
|
||||
}
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
|
||||
return false
|
||||
}
|
||||
if volumeSpec.PersistentVolume == nil {
|
||||
return false
|
||||
}
|
||||
if len(volumeSpec.PersistentVolume.Spec.AccessModes) != 1 {
|
||||
return false
|
||||
}
|
||||
if !v1helper.ContainsAccessMode(volumeSpec.PersistentVolume.Spec.AccessModes, v1.ReadWriteOncePod) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// AddSELinuxMountOption adds -o context="XYZ" mount option to a given list
|
||||
func AddSELinuxMountOption(options []string, seLinuxContext string) []string {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
|
||||
return options
|
||||
}
|
||||
// Use double quotes to support a comma "," in the SELinux context string.
|
||||
// For example: dirsync,context="system_u:object_r:container_file_t:s0:c15,c25",noatime
|
||||
return append(options, fmt.Sprintf("context=%q", seLinuxContext))
|
||||
}
|
76
vendor/k8s.io/kubernetes/pkg/volume/util/storageclass.go
generated
vendored
Normal file
76
vendor/k8s.io/kubernetes/pkg/volume/util/storageclass.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
storagev1listers "k8s.io/client-go/listers/storage/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// isDefaultStorageClassAnnotation represents a StorageClass annotation that
|
||||
// marks a class as the default StorageClass
|
||||
IsDefaultStorageClassAnnotation = "storageclass.kubernetes.io/is-default-class"
|
||||
|
||||
// betaIsDefaultStorageClassAnnotation is the beta version of IsDefaultStorageClassAnnotation.
|
||||
// TODO: remove Beta when no longer used
|
||||
BetaIsDefaultStorageClassAnnotation = "storageclass.beta.kubernetes.io/is-default-class"
|
||||
)
|
||||
|
||||
// GetDefaultClass returns the default StorageClass from the store, or nil.
|
||||
func GetDefaultClass(lister storagev1listers.StorageClassLister) (*storagev1.StorageClass, error) {
|
||||
list, err := lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultClasses := []*storagev1.StorageClass{}
|
||||
for _, class := range list {
|
||||
if IsDefaultAnnotation(class.ObjectMeta) {
|
||||
defaultClasses = append(defaultClasses, class)
|
||||
klog.V(4).Infof("GetDefaultClass added: %s", class.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(defaultClasses) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(defaultClasses) > 1 {
|
||||
klog.V(4).Infof("GetDefaultClass %d defaults found", len(defaultClasses))
|
||||
return nil, errors.NewInternalError(fmt.Errorf("%d default StorageClasses were found", len(defaultClasses)))
|
||||
}
|
||||
return defaultClasses[0], nil
|
||||
}
|
||||
|
||||
// IsDefaultAnnotation returns a boolean if the default storage class
|
||||
// annotation is set
|
||||
// TODO: remove Beta when no longer needed
|
||||
func IsDefaultAnnotation(obj metav1.ObjectMeta) bool {
|
||||
if obj.Annotations[IsDefaultStorageClassAnnotation] == "true" {
|
||||
return true
|
||||
}
|
||||
if obj.Annotations[BetaIsDefaultStorageClassAnnotation] == "true" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
7
vendor/k8s.io/kubernetes/pkg/volume/util/subpath/subpath_linux.go
generated
vendored
7
vendor/k8s.io/kubernetes/pkg/volume/util/subpath/subpath_linux.go
generated
vendored
@ -566,18 +566,23 @@ func doSafeOpen(pathname string, base string) (int, error) {
|
||||
// Follow the segments one by one using openat() to make
|
||||
// sure the user cannot change already existing directories into symlinks.
|
||||
for _, seg := range segments {
|
||||
var deviceStat unix.Stat_t
|
||||
|
||||
currentPath = filepath.Join(currentPath, seg)
|
||||
if !mount.PathWithinBase(currentPath, base) {
|
||||
return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
|
||||
}
|
||||
|
||||
// Trigger auto mount if it's an auto-mounted directory, ignore error if not a directory.
|
||||
// Notice the trailing slash is mandatory, see "automount" in openat(2) and open_by_handle_at(2).
|
||||
unix.Fstatat(parentFD, seg+"/", &deviceStat, unix.AT_SYMLINK_NOFOLLOW)
|
||||
|
||||
klog.V(5).Infof("Opening path %s", currentPath)
|
||||
childFD, err = syscall.Openat(parentFD, seg, openFDFlags|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("cannot open %s: %s", currentPath, err)
|
||||
}
|
||||
|
||||
var deviceStat unix.Stat_t
|
||||
err := unix.Fstat(childFD, &deviceStat)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error running fstat on %s with %v", currentPath, err)
|
||||
|
754
vendor/k8s.io/kubernetes/pkg/volume/util/util.go
generated
vendored
Normal file
754
vendor/k8s.io/kubernetes/pkg/volume/util/util.go
generated
vendored
Normal file
@ -0,0 +1,754 @@
|
||||
/*
|
||||
Copyright 2015 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 util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
storage "k8s.io/api/storage/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
apiruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
utypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
storagehelpers "k8s.io/component-helpers/storage/volume"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
|
||||
"k8s.io/mount-utils"
|
||||
utilexec "k8s.io/utils/exec"
|
||||
"k8s.io/utils/io"
|
||||
utilstrings "k8s.io/utils/strings"
|
||||
)
|
||||
|
||||
const (
|
||||
readyFileName = "ready"
|
||||
|
||||
// ControllerManagedAttachAnnotation is the key of the annotation on Node
|
||||
// objects that indicates attach/detach operations for the node should be
|
||||
// managed by the attach/detach controller
|
||||
ControllerManagedAttachAnnotation string = "volumes.kubernetes.io/controller-managed-attach-detach"
|
||||
|
||||
// KeepTerminatedPodVolumesAnnotation is the key of the annotation on Node
|
||||
// that decides if pod volumes are unmounted when pod is terminated
|
||||
KeepTerminatedPodVolumesAnnotation string = "volumes.kubernetes.io/keep-terminated-pod-volumes"
|
||||
|
||||
// MountsInGlobalPDPath is name of the directory appended to a volume plugin
|
||||
// name to create the place for volume mounts in the global PD path.
|
||||
MountsInGlobalPDPath = "mounts"
|
||||
|
||||
// VolumeGidAnnotationKey is the of the annotation on the PersistentVolume
|
||||
// object that specifies a supplemental GID.
|
||||
VolumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
|
||||
|
||||
// VolumeDynamicallyCreatedByKey is the key of the annotation on PersistentVolume
|
||||
// object created dynamically
|
||||
VolumeDynamicallyCreatedByKey = "kubernetes.io/createdby"
|
||||
)
|
||||
|
||||
// IsReady checks for the existence of a regular file
|
||||
// called 'ready' in the given directory and returns
|
||||
// true if that file exists.
|
||||
func IsReady(dir string) bool {
|
||||
readyFile := filepath.Join(dir, readyFileName)
|
||||
s, err := os.Stat(readyFile)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !s.Mode().IsRegular() {
|
||||
klog.Errorf("ready-file is not a file: %s", readyFile)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// SetReady creates a file called 'ready' in the given
|
||||
// directory. It logs an error if the file cannot be
|
||||
// created.
|
||||
func SetReady(dir string) {
|
||||
if err := os.MkdirAll(dir, 0750); err != nil && !os.IsExist(err) {
|
||||
klog.Errorf("Can't mkdir %s: %v", dir, err)
|
||||
return
|
||||
}
|
||||
|
||||
readyFile := filepath.Join(dir, readyFileName)
|
||||
file, err := os.Create(readyFile)
|
||||
if err != nil {
|
||||
klog.Errorf("Can't touch %s: %v", readyFile, err)
|
||||
return
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
// GetSecretForPod locates secret by name in the pod's namespace and returns secret map
|
||||
func GetSecretForPod(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (map[string]string, error) {
|
||||
secret := make(map[string]string)
|
||||
if kubeClient == nil {
|
||||
return secret, fmt.Errorf("cannot get kube client")
|
||||
}
|
||||
secrets, err := kubeClient.CoreV1().Secrets(pod.Namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return secret, err
|
||||
}
|
||||
for name, data := range secrets.Data {
|
||||
secret[name] = string(data)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// GetSecretForPV locates secret by name and namespace, verifies the secret type, and returns secret map
|
||||
func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeClient clientset.Interface) (map[string]string, error) {
|
||||
secret := make(map[string]string)
|
||||
if kubeClient == nil {
|
||||
return secret, fmt.Errorf("cannot get kube client")
|
||||
}
|
||||
secrets, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return secret, err
|
||||
}
|
||||
if secrets.Type != v1.SecretType(volumePluginName) {
|
||||
return secret, fmt.Errorf("cannot get secret of type %s", volumePluginName)
|
||||
}
|
||||
for name, data := range secrets.Data {
|
||||
secret[name] = string(data)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// GetClassForVolume locates storage class by persistent volume
|
||||
func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) (*storage.StorageClass, error) {
|
||||
if kubeClient == nil {
|
||||
return nil, fmt.Errorf("cannot get kube client")
|
||||
}
|
||||
className := storagehelpers.GetPersistentVolumeClass(pv)
|
||||
if className == "" {
|
||||
return nil, fmt.Errorf("volume has no storage class")
|
||||
}
|
||||
|
||||
class, err := kubeClient.StorageV1().StorageClasses().Get(context.TODO(), className, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return class, nil
|
||||
}
|
||||
|
||||
// LoadPodFromFile will read, decode, and return a Pod from a file.
|
||||
func LoadPodFromFile(filePath string) (*v1.Pod, error) {
|
||||
if filePath == "" {
|
||||
return nil, fmt.Errorf("file path not specified")
|
||||
}
|
||||
podDef, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file path %s: %+v", filePath, err)
|
||||
}
|
||||
if len(podDef) == 0 {
|
||||
return nil, fmt.Errorf("file was empty: %s", filePath)
|
||||
}
|
||||
pod := &v1.Pod{}
|
||||
|
||||
codec := legacyscheme.Codecs.UniversalDecoder()
|
||||
if err := apiruntime.DecodeInto(codec, podDef, pod); err != nil {
|
||||
return nil, fmt.Errorf("failed decoding file: %v", err)
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// CalculateTimeoutForVolume calculates time for a Recycler pod to complete a
|
||||
// recycle operation. The calculation and return value is either the
|
||||
// minimumTimeout or the timeoutIncrement per Gi of storage size, whichever is
|
||||
// greater.
|
||||
func CalculateTimeoutForVolume(minimumTimeout, timeoutIncrement int, pv *v1.PersistentVolume) int64 {
|
||||
giQty := resource.MustParse("1Gi")
|
||||
pvQty := pv.Spec.Capacity[v1.ResourceStorage]
|
||||
giSize := giQty.Value()
|
||||
pvSize := pvQty.Value()
|
||||
timeout := (pvSize / giSize) * int64(timeoutIncrement)
|
||||
if timeout < int64(minimumTimeout) {
|
||||
return int64(minimumTimeout)
|
||||
}
|
||||
return timeout
|
||||
}
|
||||
|
||||
// GenerateVolumeName returns a PV name with clusterName prefix. The function
|
||||
// should be used to generate a name of GCE PD or Cinder volume. It basically
|
||||
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
|
||||
// string fits given length and cuts "dynamic" if not.
|
||||
func GenerateVolumeName(clusterName, pvName string, maxLength int) string {
|
||||
prefix := clusterName + "-dynamic"
|
||||
pvLen := len(pvName)
|
||||
|
||||
// cut the "<clusterName>-dynamic" to fit full pvName into maxLength
|
||||
// +1 for the '-' dash
|
||||
if pvLen+1+len(prefix) > maxLength {
|
||||
prefix = prefix[:maxLength-pvLen-1]
|
||||
}
|
||||
return prefix + "-" + pvName
|
||||
}
|
||||
|
||||
// GetPath checks if the path from the mounter is empty.
|
||||
func GetPath(mounter volume.Mounter) (string, error) {
|
||||
path := mounter.GetPath()
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("path is empty %s", reflect.TypeOf(mounter).String())
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// UnmountViaEmptyDir delegates the tear down operation for secret, configmap, git_repo and downwardapi
|
||||
// to empty_dir
|
||||
func UnmountViaEmptyDir(dir string, host volume.VolumeHost, volName string, volSpec volume.Spec, podUID utypes.UID) error {
|
||||
klog.V(3).Infof("Tearing down volume %v for pod %v at %v", volName, podUID, dir)
|
||||
|
||||
// Wrap EmptyDir, let it do the teardown.
|
||||
wrapped, err := host.NewWrapperUnmounter(volName, volSpec, podUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return wrapped.TearDownAt(dir)
|
||||
}
|
||||
|
||||
// MountOptionFromSpec extracts and joins mount options from volume spec with supplied options
|
||||
func MountOptionFromSpec(spec *volume.Spec, options ...string) []string {
|
||||
pv := spec.PersistentVolume
|
||||
|
||||
if pv != nil {
|
||||
// Use beta annotation first
|
||||
if mo, ok := pv.Annotations[v1.MountOptionAnnotation]; ok {
|
||||
moList := strings.Split(mo, ",")
|
||||
return JoinMountOptions(moList, options)
|
||||
}
|
||||
|
||||
if len(pv.Spec.MountOptions) > 0 {
|
||||
return JoinMountOptions(pv.Spec.MountOptions, options)
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// JoinMountOptions joins mount options eliminating duplicates
|
||||
func JoinMountOptions(userOptions []string, systemOptions []string) []string {
|
||||
allMountOptions := sets.NewString()
|
||||
|
||||
for _, mountOption := range userOptions {
|
||||
if len(mountOption) > 0 {
|
||||
allMountOptions.Insert(mountOption)
|
||||
}
|
||||
}
|
||||
|
||||
for _, mountOption := range systemOptions {
|
||||
allMountOptions.Insert(mountOption)
|
||||
}
|
||||
return allMountOptions.List()
|
||||
}
|
||||
|
||||
// ContainsAccessMode returns whether the requested mode is contained by modes
|
||||
func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
||||
for _, m := range modes {
|
||||
if m == mode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAllAccessModes returns whether all of the requested modes are contained by modes
|
||||
func ContainsAllAccessModes(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool {
|
||||
for _, mode := range requestedModes {
|
||||
if !ContainsAccessMode(indexedModes, mode) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetWindowsPath get a windows path
|
||||
func GetWindowsPath(path string) string {
|
||||
windowsPath := strings.Replace(path, "/", "\\", -1)
|
||||
if strings.HasPrefix(windowsPath, "\\") {
|
||||
windowsPath = "c:" + windowsPath
|
||||
}
|
||||
return windowsPath
|
||||
}
|
||||
|
||||
// GetUniquePodName returns a unique identifier to reference a pod by
|
||||
func GetUniquePodName(pod *v1.Pod) types.UniquePodName {
|
||||
return types.UniquePodName(pod.UID)
|
||||
}
|
||||
|
||||
// GetUniqueVolumeName returns a unique name representing the volume/plugin.
|
||||
// Caller should ensure that volumeName is a name/ID uniquely identifying the
|
||||
// actual backing device, directory, path, etc. for a particular volume.
|
||||
// The returned name can be used to uniquely reference the volume, for example,
|
||||
// to prevent operations (attach/detach or mount/unmount) from being triggered
|
||||
// on the same volume.
|
||||
func GetUniqueVolumeName(pluginName, volumeName string) v1.UniqueVolumeName {
|
||||
return v1.UniqueVolumeName(fmt.Sprintf("%s/%s", pluginName, volumeName))
|
||||
}
|
||||
|
||||
// GetUniqueVolumeNameFromSpecWithPod returns a unique volume name with pod
|
||||
// name included. This is useful to generate different names for different pods
|
||||
// on same volume.
|
||||
func GetUniqueVolumeNameFromSpecWithPod(
|
||||
podName types.UniquePodName, volumePlugin volume.VolumePlugin, volumeSpec *volume.Spec) v1.UniqueVolumeName {
|
||||
return v1.UniqueVolumeName(
|
||||
fmt.Sprintf("%s/%v-%s", volumePlugin.GetPluginName(), podName, volumeSpec.Name()))
|
||||
}
|
||||
|
||||
// GetUniqueVolumeNameFromSpec uses the given VolumePlugin to generate a unique
|
||||
// name representing the volume defined in the specified volume spec.
|
||||
// This returned name can be used to uniquely reference the actual backing
|
||||
// device, directory, path, etc. referenced by the given volumeSpec.
|
||||
// If the given plugin does not support the volume spec, this returns an error.
|
||||
func GetUniqueVolumeNameFromSpec(
|
||||
volumePlugin volume.VolumePlugin,
|
||||
volumeSpec *volume.Spec) (v1.UniqueVolumeName, error) {
|
||||
if volumePlugin == nil {
|
||||
return "", fmt.Errorf(
|
||||
"volumePlugin should not be nil. volumeSpec.Name=%q",
|
||||
volumeSpec.Name())
|
||||
}
|
||||
|
||||
volumeName, err := volumePlugin.GetVolumeName(volumeSpec)
|
||||
if err != nil || volumeName == "" {
|
||||
return "", fmt.Errorf(
|
||||
"failed to GetVolumeName from volumePlugin for volumeSpec %q err=%v",
|
||||
volumeSpec.Name(),
|
||||
err)
|
||||
}
|
||||
|
||||
return GetUniqueVolumeName(
|
||||
volumePlugin.GetPluginName(),
|
||||
volumeName),
|
||||
nil
|
||||
}
|
||||
|
||||
// IsPodTerminated checks if pod is terminated
|
||||
func IsPodTerminated(pod *v1.Pod, podStatus v1.PodStatus) bool {
|
||||
// TODO: the guarantees provided by kubelet status are not sufficient to guarantee it's safe to ignore a deleted pod,
|
||||
// even if everything is notRunning (kubelet does not guarantee that when pod status is waiting that it isn't trying
|
||||
// to start a container).
|
||||
return podStatus.Phase == v1.PodFailed || podStatus.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(podStatus.InitContainerStatuses) && notRunning(podStatus.ContainerStatuses) && notRunning(podStatus.EphemeralContainerStatuses))
|
||||
}
|
||||
|
||||
// notRunning returns true if every status is terminated or waiting, or the status list
|
||||
// is empty.
|
||||
func notRunning(statuses []v1.ContainerStatus) bool {
|
||||
for _, status := range statuses {
|
||||
if status.State.Terminated == nil && status.State.Waiting == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SplitUniqueName splits the unique name to plugin name and volume name strings. It expects the uniqueName to follow
|
||||
// the format plugin_name/volume_name and the plugin name must be namespaced as described by the plugin interface,
|
||||
// i.e. namespace/plugin containing exactly one '/'. This means the unique name will always be in the form of
|
||||
// plugin_namespace/plugin/volume_name, see k8s.io/kubernetes/pkg/volume/plugins.go VolumePlugin interface
|
||||
// description and pkg/volume/util/volumehelper/volumehelper.go GetUniqueVolumeNameFromSpec that constructs
|
||||
// the unique volume names.
|
||||
func SplitUniqueName(uniqueName v1.UniqueVolumeName) (string, string, error) {
|
||||
components := strings.SplitN(string(uniqueName), "/", 3)
|
||||
if len(components) != 3 {
|
||||
return "", "", fmt.Errorf("cannot split volume unique name %s to plugin/volume components", uniqueName)
|
||||
}
|
||||
pluginName := fmt.Sprintf("%s/%s", components[0], components[1])
|
||||
return pluginName, components[2], nil
|
||||
}
|
||||
|
||||
// NewSafeFormatAndMountFromHost creates a new SafeFormatAndMount with Mounter
|
||||
// and Exec taken from given VolumeHost.
|
||||
func NewSafeFormatAndMountFromHost(pluginName string, host volume.VolumeHost) *mount.SafeFormatAndMount {
|
||||
mounter := host.GetMounter(pluginName)
|
||||
exec := host.GetExec(pluginName)
|
||||
return &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}
|
||||
}
|
||||
|
||||
// GetVolumeMode retrieves VolumeMode from pv.
|
||||
// If the volume doesn't have PersistentVolume, it's an inline volume,
|
||||
// should return volumeMode as filesystem to keep existing behavior.
|
||||
func GetVolumeMode(volumeSpec *volume.Spec) (v1.PersistentVolumeMode, error) {
|
||||
if volumeSpec == nil || volumeSpec.PersistentVolume == nil {
|
||||
return v1.PersistentVolumeFilesystem, nil
|
||||
}
|
||||
if volumeSpec.PersistentVolume.Spec.VolumeMode != nil {
|
||||
return *volumeSpec.PersistentVolume.Spec.VolumeMode, nil
|
||||
}
|
||||
return "", fmt.Errorf("cannot get volumeMode for volume: %v", volumeSpec.Name())
|
||||
}
|
||||
|
||||
// GetPersistentVolumeClaimQualifiedName returns a qualified name for pvc.
|
||||
func GetPersistentVolumeClaimQualifiedName(claim *v1.PersistentVolumeClaim) string {
|
||||
return utilstrings.JoinQualifiedName(claim.GetNamespace(), claim.GetName())
|
||||
}
|
||||
|
||||
// CheckVolumeModeFilesystem checks VolumeMode.
|
||||
// If the mode is Filesystem, return true otherwise return false.
|
||||
func CheckVolumeModeFilesystem(volumeSpec *volume.Spec) (bool, error) {
|
||||
volumeMode, err := GetVolumeMode(volumeSpec)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if volumeMode == v1.PersistentVolumeBlock {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckPersistentVolumeClaimModeBlock checks VolumeMode.
|
||||
// If the mode is Block, return true otherwise return false.
|
||||
func CheckPersistentVolumeClaimModeBlock(pvc *v1.PersistentVolumeClaim) bool {
|
||||
return pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == v1.PersistentVolumeBlock
|
||||
}
|
||||
|
||||
// IsWindowsUNCPath checks if path is prefixed with \\
|
||||
// This can be used to skip any processing of paths
|
||||
// that point to SMB shares, local named pipes and local UNC path
|
||||
func IsWindowsUNCPath(goos, path string) bool {
|
||||
if goos != "windows" {
|
||||
return false
|
||||
}
|
||||
// Check for UNC prefix \\
|
||||
if strings.HasPrefix(path, `\\`) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsWindowsLocalPath checks if path is a local path
|
||||
// prefixed with "/" or "\" like "/foo/bar" or "\foo\bar"
|
||||
func IsWindowsLocalPath(goos, path string) bool {
|
||||
if goos != "windows" {
|
||||
return false
|
||||
}
|
||||
if IsWindowsUNCPath(goos, path) {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(path, ":") {
|
||||
return false
|
||||
}
|
||||
if !(strings.HasPrefix(path, `/`) || strings.HasPrefix(path, `\`)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MakeAbsolutePath convert path to absolute path according to GOOS
|
||||
func MakeAbsolutePath(goos, path string) string {
|
||||
if goos != "windows" {
|
||||
return filepath.Clean("/" + path)
|
||||
}
|
||||
// These are all for windows
|
||||
// If there is a colon, give up.
|
||||
if strings.Contains(path, ":") {
|
||||
return path
|
||||
}
|
||||
// If there is a slash, but no drive, add 'c:'
|
||||
if strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\") {
|
||||
return "c:" + path
|
||||
}
|
||||
// Otherwise, add 'c:\'
|
||||
return "c:\\" + path
|
||||
}
|
||||
|
||||
// MapBlockVolume is a utility function to provide a common way of mapping
|
||||
// block device path for a specified volume and pod. This function should be
|
||||
// called by volume plugins that implements volume.BlockVolumeMapper.Map() method.
|
||||
func MapBlockVolume(
|
||||
blkUtil volumepathhandler.BlockVolumePathHandler,
|
||||
devicePath,
|
||||
globalMapPath,
|
||||
podVolumeMapPath,
|
||||
volumeMapName string,
|
||||
podUID utypes.UID,
|
||||
) error {
|
||||
// map devicePath to global node path as bind mount
|
||||
mapErr := blkUtil.MapDevice(devicePath, globalMapPath, string(podUID), true /* bindMount */)
|
||||
if mapErr != nil {
|
||||
return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, globalMapPath:%s, podUID: %s, bindMount: %v: %v",
|
||||
devicePath, globalMapPath, string(podUID), true, mapErr)
|
||||
}
|
||||
|
||||
// map devicePath to pod volume path
|
||||
mapErr = blkUtil.MapDevice(devicePath, podVolumeMapPath, volumeMapName, false /* bindMount */)
|
||||
if mapErr != nil {
|
||||
return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, podVolumeMapPath:%s, volumeMapName: %s, bindMount: %v: %v",
|
||||
devicePath, podVolumeMapPath, volumeMapName, false, mapErr)
|
||||
}
|
||||
|
||||
// Take file descriptor lock to keep a block device opened. Otherwise, there is a case
|
||||
// that the block device is silently removed and attached another device with the same name.
|
||||
// Container runtime can't handle this problem. To avoid unexpected condition fd lock
|
||||
// for the block device is required.
|
||||
_, mapErr = blkUtil.AttachFileDevice(filepath.Join(globalMapPath, string(podUID)))
|
||||
if mapErr != nil {
|
||||
return fmt.Errorf("blkUtil.AttachFileDevice failed. globalMapPath:%s, podUID: %s: %v",
|
||||
globalMapPath, string(podUID), mapErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmapBlockVolume is a utility function to provide a common way of unmapping
|
||||
// block device path for a specified volume and pod. This function should be
|
||||
// called by volume plugins that implements volume.BlockVolumeMapper.Map() method.
|
||||
func UnmapBlockVolume(
|
||||
blkUtil volumepathhandler.BlockVolumePathHandler,
|
||||
globalUnmapPath,
|
||||
podDeviceUnmapPath,
|
||||
volumeMapName string,
|
||||
podUID utypes.UID,
|
||||
) error {
|
||||
// Release file descriptor lock.
|
||||
err := blkUtil.DetachFileDevice(filepath.Join(globalUnmapPath, string(podUID)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s: %v",
|
||||
globalUnmapPath, string(podUID), err)
|
||||
}
|
||||
|
||||
// unmap devicePath from pod volume path
|
||||
unmapDeviceErr := blkUtil.UnmapDevice(podDeviceUnmapPath, volumeMapName, false /* bindMount */)
|
||||
if unmapDeviceErr != nil {
|
||||
return fmt.Errorf("blkUtil.DetachFileDevice failed. podDeviceUnmapPath:%s, volumeMapName: %s, bindMount: %v: %v",
|
||||
podDeviceUnmapPath, volumeMapName, false, unmapDeviceErr)
|
||||
}
|
||||
|
||||
// unmap devicePath from global node path
|
||||
unmapDeviceErr = blkUtil.UnmapDevice(globalUnmapPath, string(podUID), true /* bindMount */)
|
||||
if unmapDeviceErr != nil {
|
||||
return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s, bindMount: %v: %v",
|
||||
globalUnmapPath, string(podUID), true, unmapDeviceErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPluginMountDir returns the global mount directory name appended
|
||||
// to the given plugin name's plugin directory
|
||||
func GetPluginMountDir(host volume.VolumeHost, name string) string {
|
||||
mntDir := filepath.Join(host.GetPluginDir(name), MountsInGlobalPDPath)
|
||||
return mntDir
|
||||
}
|
||||
|
||||
// IsLocalEphemeralVolume determines whether the argument is a local ephemeral
|
||||
// volume vs. some other type
|
||||
// Local means the volume is using storage from the local disk that is managed by kubelet.
|
||||
// Ephemeral means the lifecycle of the volume is the same as the Pod.
|
||||
func IsLocalEphemeralVolume(volume v1.Volume) bool {
|
||||
return volume.GitRepo != nil ||
|
||||
(volume.EmptyDir != nil && volume.EmptyDir.Medium == v1.StorageMediumDefault) ||
|
||||
volume.ConfigMap != nil
|
||||
}
|
||||
|
||||
// GetPodVolumeNames returns names of volumes that are used in a pod,
|
||||
// either as filesystem mount or raw block device, together with list
|
||||
// of all SELinux contexts of all containers that use the volumes.
|
||||
func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String, seLinuxContainerContexts map[string][]*v1.SELinuxOptions) {
|
||||
mounts = sets.NewString()
|
||||
devices = sets.NewString()
|
||||
seLinuxContainerContexts = make(map[string][]*v1.SELinuxOptions)
|
||||
|
||||
podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(container *v1.Container, containerType podutil.ContainerType) bool {
|
||||
var seLinuxOptions *v1.SELinuxOptions
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
|
||||
effectiveContainerSecurity := securitycontext.DetermineEffectiveSecurityContext(pod, container)
|
||||
if effectiveContainerSecurity != nil {
|
||||
// No DeepCopy, SELinuxOptions is already a copy of Pod's or container's SELinuxOptions
|
||||
seLinuxOptions = effectiveContainerSecurity.SELinuxOptions
|
||||
}
|
||||
}
|
||||
|
||||
if container.VolumeMounts != nil {
|
||||
for _, mount := range container.VolumeMounts {
|
||||
mounts.Insert(mount.Name)
|
||||
if seLinuxOptions != nil {
|
||||
seLinuxContainerContexts[mount.Name] = append(seLinuxContainerContexts[mount.Name], seLinuxOptions.DeepCopy())
|
||||
}
|
||||
}
|
||||
}
|
||||
if container.VolumeDevices != nil {
|
||||
for _, device := range container.VolumeDevices {
|
||||
devices.Insert(device.Name)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// FsUserFrom returns FsUser of pod, which is determined by the runAsUser
|
||||
// attributes.
|
||||
func FsUserFrom(pod *v1.Pod) *int64 {
|
||||
var fsUser *int64
|
||||
// Exclude ephemeral containers because SecurityContext is not allowed.
|
||||
podutil.VisitContainers(&pod.Spec, podutil.InitContainers|podutil.Containers, func(container *v1.Container, containerType podutil.ContainerType) bool {
|
||||
runAsUser, ok := securitycontext.DetermineEffectiveRunAsUser(pod, container)
|
||||
// One container doesn't specify user or there are more than one
|
||||
// non-root UIDs.
|
||||
if !ok || (fsUser != nil && *fsUser != *runAsUser) {
|
||||
fsUser = nil
|
||||
return false
|
||||
}
|
||||
if fsUser == nil {
|
||||
fsUser = runAsUser
|
||||
}
|
||||
return true
|
||||
})
|
||||
return fsUser
|
||||
}
|
||||
|
||||
// HasMountRefs checks if the given mountPath has mountRefs.
|
||||
// TODO: this is a workaround for the unmount device issue caused by gci mounter.
|
||||
// In GCI cluster, if gci mounter is used for mounting, the container started by mounter
|
||||
// script will cause additional mounts created in the container. Since these mounts are
|
||||
// irrelevant to the original mounts, they should be not considered when checking the
|
||||
// mount references. Current solution is to filter out those mount paths that contain
|
||||
// the string of original mount path.
|
||||
// Plan to work on better approach to solve this issue.
|
||||
func HasMountRefs(mountPath string, mountRefs []string) bool {
|
||||
for _, ref := range mountRefs {
|
||||
if !strings.Contains(ref, mountPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WriteVolumeCache flush disk data given the spcified mount path
|
||||
func WriteVolumeCache(deviceMountPath string, exec utilexec.Interface) error {
|
||||
// If runtime os is windows, execute Write-VolumeCache powershell command on the disk
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := fmt.Sprintf("Get-Volume -FilePath %s | Write-Volumecache", deviceMountPath)
|
||||
output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
|
||||
klog.Infof("command (%q) execeuted: %v, output: %q", cmd, err, string(output))
|
||||
if err != nil {
|
||||
return fmt.Errorf("command (%q) failed: %v, output: %q", cmd, err, string(output))
|
||||
}
|
||||
}
|
||||
// For linux runtime, it skips because unmount will automatically flush disk data
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsMultiAttachAllowed checks if attaching this volume to multiple nodes is definitely not allowed/possible.
|
||||
// In its current form, this function can only reliably say for which volumes it's definitely forbidden. If it returns
|
||||
// false, it is not guaranteed that multi-attach is actually supported by the volume type and we must rely on the
|
||||
// attacher to fail fast in such cases.
|
||||
// Please see https://github.com/kubernetes/kubernetes/issues/40669 and https://github.com/kubernetes/kubernetes/pull/40148#discussion_r98055047
|
||||
func IsMultiAttachAllowed(volumeSpec *volume.Spec) bool {
|
||||
if volumeSpec == nil {
|
||||
// we don't know if it's supported or not and let the attacher fail later in cases it's not supported
|
||||
return true
|
||||
}
|
||||
|
||||
if volumeSpec.Volume != nil {
|
||||
// Check for volume types which are known to fail slow or cause trouble when trying to multi-attach
|
||||
if volumeSpec.Volume.AzureDisk != nil ||
|
||||
volumeSpec.Volume.Cinder != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Only if this volume is a persistent volume, we have reliable information on whether it's allowed or not to
|
||||
// multi-attach. We trust in the individual volume implementations to not allow unsupported access modes
|
||||
if volumeSpec.PersistentVolume != nil {
|
||||
// Check for persistent volume types which do not fail when trying to multi-attach
|
||||
if len(volumeSpec.PersistentVolume.Spec.AccessModes) == 0 {
|
||||
// No access mode specified so we don't know for sure. Let the attacher fail if needed
|
||||
return true
|
||||
}
|
||||
|
||||
// check if this volume is allowed to be attached to multiple PODs/nodes, if yes, return false
|
||||
for _, accessMode := range volumeSpec.PersistentVolume.Spec.AccessModes {
|
||||
if accessMode == v1.ReadWriteMany || accessMode == v1.ReadOnlyMany {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// we don't know if it's supported or not and let the attacher fail later in cases it's not supported
|
||||
return true
|
||||
}
|
||||
|
||||
// IsAttachableVolume checks if the given volumeSpec is an attachable volume or not
|
||||
func IsAttachableVolume(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) bool {
|
||||
attachableVolumePlugin, _ := volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
|
||||
if attachableVolumePlugin != nil {
|
||||
volumeAttacher, err := attachableVolumePlugin.NewAttacher()
|
||||
if err == nil && volumeAttacher != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDeviceMountableVolume checks if the given volumeSpec is an device mountable volume or not
|
||||
func IsDeviceMountableVolume(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) bool {
|
||||
deviceMountableVolumePlugin, _ := volumePluginMgr.FindDeviceMountablePluginBySpec(volumeSpec)
|
||||
if deviceMountableVolumePlugin != nil {
|
||||
volumeDeviceMounter, err := deviceMountableVolumePlugin.NewDeviceMounter()
|
||||
if err == nil && volumeDeviceMounter != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetReliableMountRefs calls mounter.GetMountRefs and retries on IsInconsistentReadError.
|
||||
// To be used in volume reconstruction of volume plugins that don't have any protection
|
||||
// against mounting a single volume on multiple nodes (such as attach/detach).
|
||||
func GetReliableMountRefs(mounter mount.Interface, mountPath string) ([]string, error) {
|
||||
var paths []string
|
||||
var lastErr error
|
||||
err := wait.PollImmediate(10*time.Millisecond, time.Minute, func() (bool, error) {
|
||||
var err error
|
||||
paths, err = mounter.GetMountRefs(mountPath)
|
||||
if io.IsInconsistentReadError(err) {
|
||||
lastErr = err
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err == wait.ErrWaitTimeout {
|
||||
return nil, lastErr
|
||||
}
|
||||
return paths, err
|
||||
}
|
296
vendor/k8s.io/kubernetes/pkg/volume/util/volumepathhandler/volume_path_handler.go
generated
vendored
Normal file
296
vendor/k8s.io/kubernetes/pkg/volume/util/volumepathhandler/volume_path_handler.go
generated
vendored
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
Copyright 2018 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 volumepathhandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/mount-utils"
|
||||
utilexec "k8s.io/utils/exec"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
losetupPath = "losetup"
|
||||
ErrDeviceNotFound = "device not found"
|
||||
)
|
||||
|
||||
// BlockVolumePathHandler defines a set of operations for handling block volume-related operations
|
||||
type BlockVolumePathHandler interface {
|
||||
// MapDevice creates a symbolic link to block device under specified map path
|
||||
MapDevice(devicePath string, mapPath string, linkName string, bindMount bool) error
|
||||
// UnmapDevice removes a symbolic link to block device under specified map path
|
||||
UnmapDevice(mapPath string, linkName string, bindMount bool) error
|
||||
// RemovePath removes a file or directory on specified map path
|
||||
RemoveMapPath(mapPath string) error
|
||||
// IsSymlinkExist retruns true if specified symbolic link exists
|
||||
IsSymlinkExist(mapPath string) (bool, error)
|
||||
// IsDeviceBindMountExist retruns true if specified bind mount exists
|
||||
IsDeviceBindMountExist(mapPath string) (bool, error)
|
||||
// GetDeviceBindMountRefs searches bind mounts under global map path
|
||||
GetDeviceBindMountRefs(devPath string, mapPath string) ([]string, error)
|
||||
// FindGlobalMapPathUUIDFromPod finds {pod uuid} symbolic link under globalMapPath
|
||||
// corresponding to map path symlink, and then return global map path with pod uuid.
|
||||
FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error)
|
||||
// AttachFileDevice takes a path to a regular file and makes it available as an
|
||||
// attached block device.
|
||||
AttachFileDevice(path string) (string, error)
|
||||
// DetachFileDevice takes a path to the attached block device and
|
||||
// detach it from block device.
|
||||
DetachFileDevice(path string) error
|
||||
// GetLoopDevice returns the full path to the loop device associated with the given path.
|
||||
GetLoopDevice(path string) (string, error)
|
||||
}
|
||||
|
||||
// NewBlockVolumePathHandler returns a new instance of BlockVolumeHandler.
|
||||
func NewBlockVolumePathHandler() BlockVolumePathHandler {
|
||||
var volumePathHandler VolumePathHandler
|
||||
return volumePathHandler
|
||||
}
|
||||
|
||||
// VolumePathHandler is path related operation handlers for block volume
|
||||
type VolumePathHandler struct {
|
||||
}
|
||||
|
||||
// MapDevice creates a symbolic link to block device under specified map path
|
||||
func (v VolumePathHandler) MapDevice(devicePath string, mapPath string, linkName string, bindMount bool) error {
|
||||
// Example of global map path:
|
||||
// globalMapPath/linkName: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{podUid}
|
||||
// linkName: {podUid}
|
||||
//
|
||||
// Example of pod device map path:
|
||||
// podDeviceMapPath/linkName: pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName}
|
||||
// linkName: {volumeName}
|
||||
if len(devicePath) == 0 {
|
||||
return fmt.Errorf("failed to map device to map path. devicePath is empty")
|
||||
}
|
||||
if len(mapPath) == 0 {
|
||||
return fmt.Errorf("failed to map device to map path. mapPath is empty")
|
||||
}
|
||||
if !filepath.IsAbs(mapPath) {
|
||||
return fmt.Errorf("the map path should be absolute: map path: %s", mapPath)
|
||||
}
|
||||
klog.V(5).Infof("MapDevice: devicePath %s", devicePath)
|
||||
klog.V(5).Infof("MapDevice: mapPath %s", mapPath)
|
||||
klog.V(5).Infof("MapDevice: linkName %s", linkName)
|
||||
|
||||
// Check and create mapPath
|
||||
_, err := os.Stat(mapPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("cannot validate map path: %s: %v", mapPath, err)
|
||||
}
|
||||
if err = os.MkdirAll(mapPath, 0750); err != nil {
|
||||
return fmt.Errorf("failed to mkdir %s: %v", mapPath, err)
|
||||
}
|
||||
|
||||
if bindMount {
|
||||
return mapBindMountDevice(v, devicePath, mapPath, linkName)
|
||||
}
|
||||
return mapSymlinkDevice(v, devicePath, mapPath, linkName)
|
||||
}
|
||||
|
||||
func mapBindMountDevice(v VolumePathHandler, devicePath string, mapPath string, linkName string) error {
|
||||
// Check bind mount exists
|
||||
linkPath := filepath.Join(mapPath, string(linkName))
|
||||
|
||||
file, err := os.Stat(linkPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to stat file %s: %v", linkPath, err)
|
||||
}
|
||||
|
||||
// Create file
|
||||
newFile, err := os.OpenFile(linkPath, os.O_CREATE|os.O_RDWR, 0750)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file %s: %v", linkPath, err)
|
||||
}
|
||||
if err := newFile.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close file %s: %v", linkPath, err)
|
||||
}
|
||||
} else {
|
||||
// Check if device file
|
||||
// TODO: Need to check if this device file is actually the expected bind mount
|
||||
if file.Mode()&os.ModeDevice == os.ModeDevice {
|
||||
klog.Warningf("Warning: Map skipped because bind mount already exist on the path: %v", linkPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
klog.Warningf("Warning: file %s is already exist but not mounted, skip creating file", linkPath)
|
||||
}
|
||||
|
||||
// Bind mount file
|
||||
mounter := &mount.SafeFormatAndMount{Interface: mount.New(""), Exec: utilexec.New()}
|
||||
if err := mounter.MountSensitiveWithoutSystemd(devicePath, linkPath, "" /* fsType */, []string{"bind"}, nil); err != nil {
|
||||
return fmt.Errorf("failed to bind mount devicePath: %s to linkPath %s: %v", devicePath, linkPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapSymlinkDevice(v VolumePathHandler, devicePath string, mapPath string, linkName string) error {
|
||||
// Remove old symbolic link(or file) then create new one.
|
||||
// This should be done because current symbolic link is
|
||||
// stale across node reboot.
|
||||
linkPath := filepath.Join(mapPath, string(linkName))
|
||||
if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove file %s: %v", linkPath, err)
|
||||
}
|
||||
return os.Symlink(devicePath, linkPath)
|
||||
}
|
||||
|
||||
// UnmapDevice removes a symbolic link associated to block device under specified map path
|
||||
func (v VolumePathHandler) UnmapDevice(mapPath string, linkName string, bindMount bool) error {
|
||||
if len(mapPath) == 0 {
|
||||
return fmt.Errorf("failed to unmap device from map path. mapPath is empty")
|
||||
}
|
||||
klog.V(5).Infof("UnmapDevice: mapPath %s", mapPath)
|
||||
klog.V(5).Infof("UnmapDevice: linkName %s", linkName)
|
||||
|
||||
if bindMount {
|
||||
return unmapBindMountDevice(v, mapPath, linkName)
|
||||
}
|
||||
return unmapSymlinkDevice(v, mapPath, linkName)
|
||||
}
|
||||
|
||||
func unmapBindMountDevice(v VolumePathHandler, mapPath string, linkName string) error {
|
||||
// Check bind mount exists
|
||||
linkPath := filepath.Join(mapPath, string(linkName))
|
||||
if isMountExist, checkErr := v.IsDeviceBindMountExist(linkPath); checkErr != nil {
|
||||
return checkErr
|
||||
} else if !isMountExist {
|
||||
klog.Warningf("Warning: Unmap skipped because bind mount does not exist on the path: %v", linkPath)
|
||||
|
||||
// Check if linkPath still exists
|
||||
if _, err := os.Stat(linkPath); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to check if path %s exists: %v", linkPath, err)
|
||||
}
|
||||
// linkPath has already been removed
|
||||
return nil
|
||||
}
|
||||
// Remove file
|
||||
if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove file %s: %v", linkPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmount file
|
||||
mounter := &mount.SafeFormatAndMount{Interface: mount.New(""), Exec: utilexec.New()}
|
||||
if err := mounter.Unmount(linkPath); err != nil {
|
||||
return fmt.Errorf("failed to unmount linkPath %s: %v", linkPath, err)
|
||||
}
|
||||
|
||||
// Remove file
|
||||
if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove file %s: %v", linkPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmapSymlinkDevice(v VolumePathHandler, mapPath string, linkName string) error {
|
||||
// Check symbolic link exists
|
||||
linkPath := filepath.Join(mapPath, string(linkName))
|
||||
if islinkExist, checkErr := v.IsSymlinkExist(linkPath); checkErr != nil {
|
||||
return checkErr
|
||||
} else if !islinkExist {
|
||||
klog.Warningf("Warning: Unmap skipped because symlink does not exist on the path: %v", linkPath)
|
||||
return nil
|
||||
}
|
||||
return os.Remove(linkPath)
|
||||
}
|
||||
|
||||
// RemoveMapPath removes a file or directory on specified map path
|
||||
func (v VolumePathHandler) RemoveMapPath(mapPath string) error {
|
||||
if len(mapPath) == 0 {
|
||||
return fmt.Errorf("failed to remove map path. mapPath is empty")
|
||||
}
|
||||
klog.V(5).Infof("RemoveMapPath: mapPath %s", mapPath)
|
||||
err := os.RemoveAll(mapPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove directory %s: %v", mapPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSymlinkExist returns true if specified file exists and the type is symbolik link.
|
||||
// If file doesn't exist, or file exists but not symbolic link, return false with no error.
|
||||
// On other cases, return false with error from Lstat().
|
||||
func (v VolumePathHandler) IsSymlinkExist(mapPath string) (bool, error) {
|
||||
fi, err := os.Lstat(mapPath)
|
||||
if err != nil {
|
||||
// If file doesn't exist, return false and no error
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
// Return error from Lstat()
|
||||
return false, fmt.Errorf("failed to Lstat file %s: %v", mapPath, err)
|
||||
}
|
||||
// If file exits and it's symbolic link, return true and no error
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
return true, nil
|
||||
}
|
||||
// If file exits but it's not symbolic link, return false and no error
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// IsDeviceBindMountExist returns true if specified file exists and the type is device.
|
||||
// If file doesn't exist, or file exists but not device, return false with no error.
|
||||
// On other cases, return false with error from Lstat().
|
||||
func (v VolumePathHandler) IsDeviceBindMountExist(mapPath string) (bool, error) {
|
||||
fi, err := os.Lstat(mapPath)
|
||||
if err != nil {
|
||||
// If file doesn't exist, return false and no error
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Return error from Lstat()
|
||||
return false, fmt.Errorf("failed to Lstat file %s: %v", mapPath, err)
|
||||
}
|
||||
// If file exits and it's device, return true and no error
|
||||
if fi.Mode()&os.ModeDevice == os.ModeDevice {
|
||||
return true, nil
|
||||
}
|
||||
// If file exits but it's not device, return false and no error
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetDeviceBindMountRefs searches bind mounts under global map path
|
||||
func (v VolumePathHandler) GetDeviceBindMountRefs(devPath string, mapPath string) ([]string, error) {
|
||||
var refs []string
|
||||
files, err := ioutil.ReadDir(mapPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.Mode()&os.ModeDevice != os.ModeDevice {
|
||||
continue
|
||||
}
|
||||
filename := file.Name()
|
||||
// TODO: Might need to check if the file is actually linked to devPath
|
||||
refs = append(refs, filepath.Join(mapPath, filename))
|
||||
}
|
||||
klog.V(5).Infof("GetDeviceBindMountRefs: refs %v", refs)
|
||||
return refs, nil
|
||||
}
|
229
vendor/k8s.io/kubernetes/pkg/volume/util/volumepathhandler/volume_path_handler_linux.go
generated
vendored
Normal file
229
vendor/k8s.io/kubernetes/pkg/volume/util/volumepathhandler/volume_path_handler_linux.go
generated
vendored
Normal file
@ -0,0 +1,229 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2018 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 volumepathhandler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// AttachFileDevice takes a path to a regular file and makes it available as an
|
||||
// attached block device.
|
||||
func (v VolumePathHandler) AttachFileDevice(path string) (string, error) {
|
||||
blockDevicePath, err := v.GetLoopDevice(path)
|
||||
if err != nil && err.Error() != ErrDeviceNotFound {
|
||||
return "", fmt.Errorf("GetLoopDevice failed for path %s: %v", path, err)
|
||||
}
|
||||
|
||||
// If no existing loop device for the path, create one
|
||||
if blockDevicePath == "" {
|
||||
klog.V(4).Infof("Creating device for path: %s", path)
|
||||
blockDevicePath, err = makeLoopDevice(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("makeLoopDevice failed for path %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
return blockDevicePath, nil
|
||||
}
|
||||
|
||||
// DetachFileDevice takes a path to the attached block device and
|
||||
// detach it from block device.
|
||||
func (v VolumePathHandler) DetachFileDevice(path string) error {
|
||||
loopPath, err := v.GetLoopDevice(path)
|
||||
if err != nil {
|
||||
if err.Error() == ErrDeviceNotFound {
|
||||
klog.Warningf("couldn't find loopback device which takes file descriptor lock. Skip detaching device. device path: %q", path)
|
||||
} else {
|
||||
return fmt.Errorf("GetLoopDevice failed for path %s: %v", path, err)
|
||||
}
|
||||
} else {
|
||||
if len(loopPath) != 0 {
|
||||
err = removeLoopDevice(loopPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("removeLoopDevice failed for path %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLoopDevice returns the full path to the loop device associated with the given path.
|
||||
func (v VolumePathHandler) GetLoopDevice(path string) (string, error) {
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return "", errors.New(ErrDeviceNotFound)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("not attachable: %v", err)
|
||||
}
|
||||
|
||||
return getLoopDeviceFromSysfs(path)
|
||||
}
|
||||
|
||||
func makeLoopDevice(path string) (string, error) {
|
||||
args := []string{"-f", path}
|
||||
cmd := exec.Command(losetupPath, args...)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Failed device create command for path: %s %v %s", path, err, out)
|
||||
return "", fmt.Errorf("losetup %s failed: %v", strings.Join(args, " "), err)
|
||||
}
|
||||
|
||||
return getLoopDeviceFromSysfs(path)
|
||||
}
|
||||
|
||||
// removeLoopDevice removes specified loopback device
|
||||
func removeLoopDevice(device string) error {
|
||||
args := []string{"-d", device}
|
||||
cmd := exec.Command(losetupPath, args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if _, err := os.Stat(device); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
klog.V(2).Infof("Failed to remove loopback device: %s: %v %s", device, err, out)
|
||||
return fmt.Errorf("losetup -d %s failed: %v", device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getLoopDeviceFromSysfs finds the backing file for a loop
|
||||
// device from sysfs via "/sys/block/loop*/loop/backing_file".
|
||||
func getLoopDeviceFromSysfs(path string) (string, error) {
|
||||
// If the file is a symlink.
|
||||
realPath, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to evaluate path %s: %s", path, err)
|
||||
}
|
||||
|
||||
devices, err := filepath.Glob("/sys/block/loop*")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to list loop devices in sysfs: %s", err)
|
||||
}
|
||||
|
||||
for _, device := range devices {
|
||||
backingFile := fmt.Sprintf("%s/loop/backing_file", device)
|
||||
|
||||
// The contents of this file is the absolute path of "path".
|
||||
data, err := ioutil.ReadFile(backingFile)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Return the first match.
|
||||
backingFilePath := strings.TrimSpace(string(data))
|
||||
if backingFilePath == path || backingFilePath == realPath {
|
||||
return fmt.Sprintf("/dev/%s", filepath.Base(device)), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New(ErrDeviceNotFound)
|
||||
}
|
||||
|
||||
// FindGlobalMapPathUUIDFromPod finds {pod uuid} bind mount under globalMapPath
|
||||
// corresponding to map path symlink, and then return global map path with pod uuid.
|
||||
// (See pkg/volume/volume.go for details on a global map path and a pod device map path.)
|
||||
// ex. mapPath symlink: pods/{podUid}}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} -> /dev/sdX
|
||||
//
|
||||
// globalMapPath/{pod uuid} bind mount: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid} -> /dev/sdX
|
||||
func (v VolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) {
|
||||
var globalMapPathUUID string
|
||||
// Find symbolic link named pod uuid under plugin dir
|
||||
err := filepath.Walk(pluginDir, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (fi.Mode()&os.ModeDevice == os.ModeDevice) && (fi.Name() == string(podUID)) {
|
||||
klog.V(5).Infof("FindGlobalMapPathFromPod: path %s, mapPath %s", path, mapPath)
|
||||
if res, err := compareBindMountAndSymlinks(path, mapPath); err == nil && res {
|
||||
globalMapPathUUID = path
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("FindGlobalMapPathUUIDFromPod failed: %v", err)
|
||||
}
|
||||
klog.V(5).Infof("FindGlobalMapPathFromPod: globalMapPathUUID %s", globalMapPathUUID)
|
||||
// Return path contains global map path + {pod uuid}
|
||||
return globalMapPathUUID, nil
|
||||
}
|
||||
|
||||
// compareBindMountAndSymlinks returns if global path (bind mount) and
|
||||
// pod path (symlink) are pointing to the same device.
|
||||
// If there is an error in checking it returns error.
|
||||
func compareBindMountAndSymlinks(global, pod string) (bool, error) {
|
||||
// To check if bind mount and symlink are pointing to the same device,
|
||||
// we need to check if they are pointing to the devices that have same major/minor number.
|
||||
|
||||
// Get the major/minor number for global path
|
||||
devNumGlobal, err := getDeviceMajorMinor(global)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getDeviceMajorMinor failed for path %s: %v", global, err)
|
||||
}
|
||||
|
||||
// Get the symlinked device from the pod path
|
||||
devPod, err := os.Readlink(pod)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to readlink path %s: %v", pod, err)
|
||||
}
|
||||
// Get the major/minor number for the symlinked device from the pod path
|
||||
devNumPod, err := getDeviceMajorMinor(devPod)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getDeviceMajorMinor failed for path %s: %v", devPod, err)
|
||||
}
|
||||
klog.V(5).Infof("CompareBindMountAndSymlinks: devNumGlobal %s, devNumPod %s", devNumGlobal, devNumPod)
|
||||
|
||||
// Check if the major/minor number are the same
|
||||
if devNumGlobal == devNumPod {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// getDeviceMajorMinor returns major/minor number for the path with below format:
|
||||
// major:minor (in hex)
|
||||
// ex)
|
||||
//
|
||||
// fc:10
|
||||
func getDeviceMajorMinor(path string) (string, error) {
|
||||
var stat unix.Stat_t
|
||||
|
||||
if err := unix.Stat(path, &stat); err != nil {
|
||||
return "", fmt.Errorf("failed to stat path %s: %v", path, err)
|
||||
}
|
||||
|
||||
devNumber := uint64(stat.Rdev)
|
||||
major := unix.Major(devNumber)
|
||||
minor := unix.Minor(devNumber)
|
||||
|
||||
return fmt.Sprintf("%x:%x", major, minor), nil
|
||||
}
|
49
vendor/k8s.io/kubernetes/pkg/volume/util/volumepathhandler/volume_path_handler_unsupported.go
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/pkg/volume/util/volumepathhandler/volume_path_handler_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2018 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 volumepathhandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// AttachFileDevice takes a path to a regular file and makes it available as an
|
||||
// attached block device.
|
||||
func (v VolumePathHandler) AttachFileDevice(path string) (string, error) {
|
||||
return "", fmt.Errorf("AttachFileDevice not supported for this build.")
|
||||
}
|
||||
|
||||
// DetachFileDevice takes a path to the attached block device and
|
||||
// detach it from block device.
|
||||
func (v VolumePathHandler) DetachFileDevice(path string) error {
|
||||
return fmt.Errorf("DetachFileDevice not supported for this build.")
|
||||
}
|
||||
|
||||
// GetLoopDevice returns the full path to the loop device associated with the given path.
|
||||
func (v VolumePathHandler) GetLoopDevice(path string) (string, error) {
|
||||
return "", fmt.Errorf("GetLoopDevice not supported for this build.")
|
||||
}
|
||||
|
||||
// FindGlobalMapPathUUIDFromPod finds {pod uuid} bind mount under globalMapPath
|
||||
// corresponding to map path symlink, and then return global map path with pod uuid.
|
||||
func (v VolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) {
|
||||
return "", fmt.Errorf("FindGlobalMapPathUUIDFromPod not supported for this build.")
|
||||
}
|
4
vendor/k8s.io/kubernetes/pkg/volume/volume.go
generated
vendored
4
vendor/k8s.io/kubernetes/pkg/volume/volume.go
generated
vendored
@ -129,6 +129,7 @@ type MounterArgs struct {
|
||||
FsGroup *int64
|
||||
FSGroupChangePolicy *v1.PodFSGroupChangePolicy
|
||||
DesiredSize *resource.Quantity
|
||||
SELinuxLabel string
|
||||
}
|
||||
|
||||
// Mounter interface provides methods to set up/mount the volume.
|
||||
@ -262,7 +263,8 @@ type Attacher interface {
|
||||
|
||||
// DeviceMounterArgs provides auxiliary, optional arguments to DeviceMounter.
|
||||
type DeviceMounterArgs struct {
|
||||
FsGroup *int64
|
||||
FsGroup *int64
|
||||
SELinuxLabel string
|
||||
}
|
||||
|
||||
// DeviceMounter can mount a block volume to a global path.
|
||||
|
Reference in New Issue
Block a user