mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 14:37:00 +00:00
DaemonSet updates
It implements https://github.com/kubernetes/community/blob/master/contributors/design-proposals/daemonset-update.md Feature https://github.com/kubernetes/features/issues/124
This commit is contained in:
parent
8124705c81
commit
b27308c317
@ -518,6 +518,25 @@ func extensionFuncs(t apitesting.TestingCommon) []interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
func(j *extensions.DaemonSetUpdateStrategy, c fuzz.Continue) {
|
||||||
|
c.FuzzNoCustom(j) // fuzz self without calling this function again
|
||||||
|
// Ensure that strategyType is one of valid values.
|
||||||
|
strategyTypes := []extensions.DaemonSetUpdateStrategyType{extensions.RollingUpdateDaemonSetStrategyType, extensions.OnDeleteDaemonSetStrategyType}
|
||||||
|
j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))]
|
||||||
|
if j.Type != extensions.RollingUpdateDaemonSetStrategyType {
|
||||||
|
j.RollingUpdate = nil
|
||||||
|
} else {
|
||||||
|
rollingUpdate := extensions.RollingUpdateDaemonSet{}
|
||||||
|
if c.RandBool() {
|
||||||
|
if c.RandBool() {
|
||||||
|
rollingUpdate.MaxUnavailable = intstr.FromInt(1 + int(c.Rand.Int31()))
|
||||||
|
} else {
|
||||||
|
rollingUpdate.MaxUnavailable = intstr.FromString(fmt.Sprintf("%d%%", 1+c.Rand.Int31()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j.RollingUpdate = &rollingUpdate
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,10 +370,9 @@ type DeploymentList struct {
|
|||||||
Items []Deployment
|
Items []Deployment
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(madhusudancs): Uncomment while implementing DaemonSet updates.
|
|
||||||
/* Commenting out for v1.2. We are planning to bring these types back with a more robust DaemonSet update implementation in v1.3, hence not deleting but just commenting the types out.
|
|
||||||
type DaemonSetUpdateStrategy struct {
|
type DaemonSetUpdateStrategy struct {
|
||||||
// Type of daemon set update. Only "RollingUpdate" is supported at this time. Default is RollingUpdate.
|
// Type of daemon set update. Can be "RollingUpdate" or "OnDelete".
|
||||||
|
// Default is OnDelete.
|
||||||
// +optional
|
// +optional
|
||||||
Type DaemonSetUpdateStrategyType
|
Type DaemonSetUpdateStrategyType
|
||||||
|
|
||||||
@ -382,6 +381,7 @@ type DaemonSetUpdateStrategy struct {
|
|||||||
//---
|
//---
|
||||||
// TODO: Update this to follow our convention for oneOf, whatever we decide it
|
// TODO: Update this to follow our convention for oneOf, whatever we decide it
|
||||||
// to be. Same as DeploymentStrategy.RollingUpdate.
|
// to be. Same as DeploymentStrategy.RollingUpdate.
|
||||||
|
// See https://github.com/kubernetes/kubernetes/issues/35345
|
||||||
// +optional
|
// +optional
|
||||||
RollingUpdate *RollingUpdateDaemonSet
|
RollingUpdate *RollingUpdateDaemonSet
|
||||||
}
|
}
|
||||||
@ -391,6 +391,9 @@ type DaemonSetUpdateStrategyType string
|
|||||||
const (
|
const (
|
||||||
// Replace the old daemons by new ones using rolling update i.e replace them on each node one after the other.
|
// Replace the old daemons by new ones using rolling update i.e replace them on each node one after the other.
|
||||||
RollingUpdateDaemonSetStrategyType DaemonSetUpdateStrategyType = "RollingUpdate"
|
RollingUpdateDaemonSetStrategyType DaemonSetUpdateStrategyType = "RollingUpdate"
|
||||||
|
|
||||||
|
// Replace the old daemons only when it's killed
|
||||||
|
OnDeleteDaemonSetStrategyType DaemonSetUpdateStrategyType = "OnDelete"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Spec to control the desired behavior of daemon set rolling update.
|
// Spec to control the desired behavior of daemon set rolling update.
|
||||||
@ -410,15 +413,7 @@ type RollingUpdateDaemonSet struct {
|
|||||||
// during the update.
|
// during the update.
|
||||||
// +optional
|
// +optional
|
||||||
MaxUnavailable intstr.IntOrString
|
MaxUnavailable intstr.IntOrString
|
||||||
|
|
||||||
// Minimum number of seconds for which a newly created DaemonSet 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).
|
|
||||||
// +optional
|
|
||||||
MinReadySeconds int
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// DaemonSetSpec is the specification of a daemon set.
|
// DaemonSetSpec is the specification of a daemon set.
|
||||||
type DaemonSetSpec struct {
|
type DaemonSetSpec struct {
|
||||||
@ -436,31 +431,18 @@ type DaemonSetSpec struct {
|
|||||||
// More info: http://kubernetes.io/docs/user-guide/replication-controller#pod-template
|
// More info: http://kubernetes.io/docs/user-guide/replication-controller#pod-template
|
||||||
Template api.PodTemplateSpec
|
Template api.PodTemplateSpec
|
||||||
|
|
||||||
// TODO(madhusudancs): Uncomment while implementing DaemonSet updates.
|
// UpdateStrategy to replace existing DaemonSet pods with new pods.
|
||||||
/* Commenting out for v1.2. We are planning to bring these fields back with a more robust DaemonSet update implementation in v1.3, hence not deleting but just commenting these fields out.
|
|
||||||
// Update strategy to replace existing DaemonSet pods with new pods.
|
|
||||||
// +optional
|
// +optional
|
||||||
UpdateStrategy DaemonSetUpdateStrategy
|
UpdateStrategy DaemonSetUpdateStrategy
|
||||||
|
|
||||||
// Label key that is added to DaemonSet pods to distinguish between old and
|
// MinReadySeconds minimum number of seconds for which a newly created DaemonSet pod should
|
||||||
// new pod templates during DaemonSet update.
|
// be ready without any of its container crashing, for it to be considered
|
||||||
// Users can set this to an empty string to indicate that the system should
|
// available. Defaults to 0 (pod will be considered available as soon as it
|
||||||
// not add any label. If unspecified, system uses
|
// is ready).
|
||||||
// DefaultDaemonSetUniqueLabelKey("daemonset.kubernetes.io/podTemplateHash").
|
|
||||||
// Value of this key is hash of DaemonSetSpec.PodTemplateSpec.
|
|
||||||
// No label is added if this is set to empty string.
|
|
||||||
// +optional
|
// +optional
|
||||||
UniqueLabelKey string
|
MinReadySeconds int32
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultDaemonSetUniqueLabelKey is the default key of the labels that is added
|
|
||||||
// to daemon set pods to distinguish between old and new pod templates during
|
|
||||||
// DaemonSet update. See DaemonSetSpec's UniqueLabelKey field for more information.
|
|
||||||
DefaultDaemonSetUniqueLabelKey string = "daemonset.kubernetes.io/podTemplateHash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DaemonSetStatus represents the current status of a daemon set.
|
// DaemonSetStatus represents the current status of a daemon set.
|
||||||
type DaemonSetStatus struct {
|
type DaemonSetStatus struct {
|
||||||
// CurrentNumberScheduled is the number of nodes that are running at least 1
|
// CurrentNumberScheduled is the number of nodes that are running at least 1
|
||||||
@ -482,6 +464,23 @@ type DaemonSetStatus struct {
|
|||||||
// ObservedGeneration is the most recent generation observed by the daemon set controller.
|
// ObservedGeneration is the most recent generation observed by the daemon set controller.
|
||||||
// +optional
|
// +optional
|
||||||
ObservedGeneration int64
|
ObservedGeneration int64
|
||||||
|
|
||||||
|
// UpdatedNumberScheduled is the total number of nodes that are running updated
|
||||||
|
// daemon pod
|
||||||
|
// +optional
|
||||||
|
UpdatedNumberScheduled int32
|
||||||
|
|
||||||
|
// NumberAvailable is the number of nodes that should be running the
|
||||||
|
// daemon pod and have one or more of the daemon pod running and
|
||||||
|
// available (ready for at least minReadySeconds)
|
||||||
|
// +optional
|
||||||
|
NumberAvailable int32
|
||||||
|
|
||||||
|
// NumberUnavailable is the number of nodes that should be running the
|
||||||
|
// daemon pod and have none of the daemon pod running and available
|
||||||
|
// (ready for at least minReadySeconds)
|
||||||
|
// +optional
|
||||||
|
NumberUnavailable int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient=true
|
// +genclient=true
|
||||||
@ -506,8 +505,20 @@ type DaemonSet struct {
|
|||||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
|
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
|
||||||
// +optional
|
// +optional
|
||||||
Status DaemonSetStatus
|
Status DaemonSetStatus
|
||||||
|
|
||||||
|
// A sequence number representing a specific generation of the template.
|
||||||
|
// Populated by the system. Read-only.
|
||||||
|
// +optional
|
||||||
|
TemplateGeneration int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DaemonSetTemplateGenerationKey is the key of the labels that is added
|
||||||
|
// to daemon set pods to distinguish between old and new pod templates
|
||||||
|
// during DaemonSet template update.
|
||||||
|
DaemonSetTemplateGenerationKey string = "pod-template-generation"
|
||||||
|
)
|
||||||
|
|
||||||
// DaemonSetList is a collection of daemon sets.
|
// DaemonSetList is a collection of daemon sets.
|
||||||
type DaemonSetList struct {
|
type DaemonSetList struct {
|
||||||
metav1.TypeMeta
|
metav1.TypeMeta
|
||||||
|
@ -38,6 +38,8 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
|
|||||||
Convert_v1beta1_DeploymentStrategy_To_extensions_DeploymentStrategy,
|
Convert_v1beta1_DeploymentStrategy_To_extensions_DeploymentStrategy,
|
||||||
Convert_extensions_RollingUpdateDeployment_To_v1beta1_RollingUpdateDeployment,
|
Convert_extensions_RollingUpdateDeployment_To_v1beta1_RollingUpdateDeployment,
|
||||||
Convert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment,
|
Convert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment,
|
||||||
|
Convert_extensions_RollingUpdateDaemonSet_To_v1beta1_RollingUpdateDaemonSet,
|
||||||
|
Convert_v1beta1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet,
|
||||||
Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec,
|
Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec,
|
||||||
Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec,
|
Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec,
|
||||||
)
|
)
|
||||||
@ -219,6 +221,23 @@ func Convert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployme
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Convert_extensions_RollingUpdateDaemonSet_To_v1beta1_RollingUpdateDaemonSet(in *extensions.RollingUpdateDaemonSet, out *RollingUpdateDaemonSet, s conversion.Scope) error {
|
||||||
|
if out.MaxUnavailable == nil {
|
||||||
|
out.MaxUnavailable = &intstr.IntOrString{}
|
||||||
|
}
|
||||||
|
if err := s.Convert(&in.MaxUnavailable, out.MaxUnavailable, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Convert_v1beta1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet(in *RollingUpdateDaemonSet, out *extensions.RollingUpdateDaemonSet, s conversion.Scope) error {
|
||||||
|
if err := s.Convert(in.MaxUnavailable, &out.MaxUnavailable, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec(in *extensions.ReplicaSetSpec, out *ReplicaSetSpec, s conversion.Scope) error {
|
func Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec(in *extensions.ReplicaSetSpec, out *ReplicaSetSpec, s conversion.Scope) error {
|
||||||
out.Replicas = new(int32)
|
out.Replicas = new(int32)
|
||||||
*out.Replicas = int32(in.Replicas)
|
*out.Replicas = int32(in.Replicas)
|
||||||
|
@ -47,6 +47,21 @@ func SetDefaults_DaemonSet(obj *DaemonSet) {
|
|||||||
obj.Labels = labels
|
obj.Labels = labels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateStrategy := &obj.Spec.UpdateStrategy
|
||||||
|
if updateStrategy.Type == "" {
|
||||||
|
updateStrategy.Type = OnDeleteDaemonSetStrategyType
|
||||||
|
}
|
||||||
|
if updateStrategy.Type == RollingUpdateDaemonSetStrategyType {
|
||||||
|
if updateStrategy.RollingUpdate == nil {
|
||||||
|
rollingUpdate := RollingUpdateDaemonSet{}
|
||||||
|
updateStrategy.RollingUpdate = &rollingUpdate
|
||||||
|
}
|
||||||
|
if updateStrategy.RollingUpdate.MaxUnavailable == nil {
|
||||||
|
// Set default MaxUnavailable as 1 by default.
|
||||||
|
maxUnavailable := intstr.FromInt(1)
|
||||||
|
updateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetDefaults_Deployment(obj *Deployment) {
|
func SetDefaults_Deployment(obj *Deployment) {
|
||||||
|
@ -74,6 +74,9 @@ func TestSetDefaultDaemonSet(t *testing.T) {
|
|||||||
MatchLabels: defaultLabels,
|
MatchLabels: defaultLabels,
|
||||||
},
|
},
|
||||||
Template: defaultTemplate,
|
Template: defaultTemplate,
|
||||||
|
UpdateStrategy: DaemonSetUpdateStrategy{
|
||||||
|
Type: OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -99,6 +102,9 @@ func TestSetDefaultDaemonSet(t *testing.T) {
|
|||||||
MatchLabels: defaultLabels,
|
MatchLabels: defaultLabels,
|
||||||
},
|
},
|
||||||
Template: defaultTemplate,
|
Template: defaultTemplate,
|
||||||
|
UpdateStrategy: DaemonSetUpdateStrategy{
|
||||||
|
Type: OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -107,6 +113,9 @@ func TestSetDefaultDaemonSet(t *testing.T) {
|
|||||||
expected: &DaemonSet{
|
expected: &DaemonSet{
|
||||||
Spec: DaemonSetSpec{
|
Spec: DaemonSetSpec{
|
||||||
Template: templateNoLabel,
|
Template: templateNoLabel,
|
||||||
|
UpdateStrategy: DaemonSetUpdateStrategy{
|
||||||
|
Type: OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -117,6 +126,9 @@ func TestSetDefaultDaemonSet(t *testing.T) {
|
|||||||
expected: &DaemonSet{
|
expected: &DaemonSet{
|
||||||
Spec: DaemonSetSpec{
|
Spec: DaemonSetSpec{
|
||||||
Template: templateNoLabel,
|
Template: templateNoLabel,
|
||||||
|
UpdateStrategy: DaemonSetUpdateStrategy{
|
||||||
|
Type: OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -127,6 +139,9 @@ func TestSetDefaultDaemonSet(t *testing.T) {
|
|||||||
expected: &DaemonSet{
|
expected: &DaemonSet{
|
||||||
Spec: DaemonSetSpec{
|
Spec: DaemonSetSpec{
|
||||||
Template: templateNoLabel,
|
Template: templateNoLabel,
|
||||||
|
UpdateStrategy: DaemonSetUpdateStrategy{
|
||||||
|
Type: OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -369,20 +369,20 @@ type DeploymentList struct {
|
|||||||
Items []Deployment `json:"items" protobuf:"bytes,2,rep,name=items"`
|
Items []Deployment `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(madhusudancs): Uncomment while implementing DaemonSet updates.
|
|
||||||
/* Commenting out for v1.2. We are planning to bring these types back with a more robust DaemonSet update implementation in v1.3, hence not deleting but just commenting the types out.
|
|
||||||
type DaemonSetUpdateStrategy struct {
|
type DaemonSetUpdateStrategy struct {
|
||||||
// Type of daemon set update. Only "RollingUpdate" is supported at this time. Default is RollingUpdate.
|
// Type of daemon set update. Can be "RollingUpdate" or "OnDelete".
|
||||||
|
// Default is OnDelete.
|
||||||
// +optional
|
// +optional
|
||||||
Type DaemonSetUpdateStrategyType `json:"type,omitempty"`
|
Type DaemonSetUpdateStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type"`
|
||||||
|
|
||||||
// Rolling update config params. Present only if DaemonSetUpdateStrategy =
|
// Rolling update config params. Present only if DaemonSetUpdateStrategy =
|
||||||
// RollingUpdate.
|
// RollingUpdate.
|
||||||
//---
|
//---
|
||||||
// TODO: Update this to follow our convention for oneOf, whatever we decide it
|
// TODO: Update this to follow our convention for oneOf, whatever we decide it
|
||||||
// to be. Same as DeploymentStrategy.RollingUpdate.
|
// to be. Same as DeploymentStrategy.RollingUpdate.
|
||||||
|
// See https://github.com/kubernetes/kubernetes/issues/35345
|
||||||
// +optional
|
// +optional
|
||||||
RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty"`
|
RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DaemonSetUpdateStrategyType string
|
type DaemonSetUpdateStrategyType string
|
||||||
@ -390,6 +390,9 @@ type DaemonSetUpdateStrategyType string
|
|||||||
const (
|
const (
|
||||||
// Replace the old daemons by new ones using rolling update i.e replace them on each node one after the other.
|
// Replace the old daemons by new ones using rolling update i.e replace them on each node one after the other.
|
||||||
RollingUpdateDaemonSetStrategyType DaemonSetUpdateStrategyType = "RollingUpdate"
|
RollingUpdateDaemonSetStrategyType DaemonSetUpdateStrategyType = "RollingUpdate"
|
||||||
|
|
||||||
|
// Replace the old daemons only when it's killed
|
||||||
|
OnDeleteDaemonSetStrategyType DaemonSetUpdateStrategyType = "OnDelete"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Spec to control the desired behavior of daemon set rolling update.
|
// Spec to control the desired behavior of daemon set rolling update.
|
||||||
@ -408,16 +411,8 @@ type RollingUpdateDaemonSet struct {
|
|||||||
// 70% of original number of DaemonSet pods are available at all times
|
// 70% of original number of DaemonSet pods are available at all times
|
||||||
// during the update.
|
// during the update.
|
||||||
// +optional
|
// +optional
|
||||||
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
|
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,1,opt,name=maxUnavailable"`
|
||||||
|
|
||||||
// Minimum number of seconds for which a newly created DaemonSet 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).
|
|
||||||
// +optional
|
|
||||||
MinReadySeconds int32 `json:"minReadySeconds,omitempty"`
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// DaemonSetSpec is the specification of a daemon set.
|
// DaemonSetSpec is the specification of a daemon set.
|
||||||
type DaemonSetSpec struct {
|
type DaemonSetSpec struct {
|
||||||
@ -435,31 +430,18 @@ type DaemonSetSpec struct {
|
|||||||
// More info: http://kubernetes.io/docs/user-guide/replication-controller#pod-template
|
// More info: http://kubernetes.io/docs/user-guide/replication-controller#pod-template
|
||||||
Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,2,opt,name=template"`
|
Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,2,opt,name=template"`
|
||||||
|
|
||||||
// TODO(madhusudancs): Uncomment while implementing DaemonSet updates.
|
// UpdateStrategy to replace existing DaemonSet pods with new pods.
|
||||||
/* Commenting out for v1.2. We are planning to bring these fields back with a more robust DaemonSet update implementation in v1.3, hence not deleting but just commenting these fields out.
|
|
||||||
// Update strategy to replace existing DaemonSet pods with new pods.
|
|
||||||
// +optional
|
// +optional
|
||||||
UpdateStrategy DaemonSetUpdateStrategy `json:"updateStrategy,omitempty"`
|
UpdateStrategy DaemonSetUpdateStrategy `json:"updateStrategy,omitempty" protobuf:"bytes,3,opt,name=updateStrategy"`
|
||||||
|
|
||||||
// Label key that is added to DaemonSet pods to distinguish between old and
|
// MinReadySeconds minimum number of seconds for which a newly created DaemonSet pod should
|
||||||
// new pod templates during DaemonSet update.
|
// be ready without any of its container crashing, for it to be considered
|
||||||
// Users can set this to an empty string to indicate that the system should
|
// available. Defaults to 0 (pod will be considered available as soon as it
|
||||||
// not add any label. If unspecified, system uses
|
// is ready).
|
||||||
// DefaultDaemonSetUniqueLabelKey("daemonset.kubernetes.io/podTemplateHash").
|
|
||||||
// Value of this key is hash of DaemonSetSpec.PodTemplateSpec.
|
|
||||||
// No label is added if this is set to empty string.
|
|
||||||
// +optional
|
// +optional
|
||||||
UniqueLabelKey *string `json:"uniqueLabelKey,omitempty"`
|
MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"`
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultDaemonSetUniqueLabelKey is the default key of the labels that is added
|
|
||||||
// to daemon set pods to distinguish between old and new pod templates during
|
|
||||||
// DaemonSet update. See DaemonSetSpec's UniqueLabelKey field for more information.
|
|
||||||
DefaultDaemonSetUniqueLabelKey string = "daemonset.kubernetes.io/podTemplateHash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DaemonSetStatus represents the current status of a daemon set.
|
// DaemonSetStatus represents the current status of a daemon set.
|
||||||
type DaemonSetStatus struct {
|
type DaemonSetStatus struct {
|
||||||
// CurrentNumberScheduled is the number of nodes that are running at least 1
|
// CurrentNumberScheduled is the number of nodes that are running at least 1
|
||||||
@ -484,6 +466,23 @@ type DaemonSetStatus struct {
|
|||||||
// ObservedGeneration is the most recent generation observed by the daemon set controller.
|
// ObservedGeneration is the most recent generation observed by the daemon set controller.
|
||||||
// +optional
|
// +optional
|
||||||
ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,5,opt,name=observedGeneration"`
|
ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,5,opt,name=observedGeneration"`
|
||||||
|
|
||||||
|
// UpdatedNumberScheduled is the total number of nodes that are running updated
|
||||||
|
// daemon pod
|
||||||
|
// +optional
|
||||||
|
UpdatedNumberScheduled int32 `json:"updatedNumberScheduled,omitempty" protobuf:"varint,6,opt,name=updatedNumberScheduled"`
|
||||||
|
|
||||||
|
// NumberAvailable is the number of nodes that should be running the
|
||||||
|
// daemon pod and have one or more of the daemon pod running and
|
||||||
|
// available (ready for at least minReadySeconds)
|
||||||
|
// +optional
|
||||||
|
NumberAvailable int32 `json:"numberAvailable,omitempty" protobuf:"varint,7,opt,name=numberAvailable"`
|
||||||
|
|
||||||
|
// NumberUnavailable is the number of nodes that should be running the
|
||||||
|
// daemon pod and have none of the daemon pod running and available
|
||||||
|
// (ready for at least minReadySeconds)
|
||||||
|
// +optional
|
||||||
|
NumberUnavailable int32 `json:"numberUnavailable,omitempty" protobuf:"varint,8,opt,name=numberUnavailable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +genclient=true
|
// +genclient=true
|
||||||
@ -508,8 +507,20 @@ type DaemonSet struct {
|
|||||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
|
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
|
||||||
// +optional
|
// +optional
|
||||||
Status DaemonSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
Status DaemonSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||||
|
|
||||||
|
// A sequence number representing a specific generation of the template.
|
||||||
|
// Populated by the system. Read-only.
|
||||||
|
// +optional
|
||||||
|
TemplateGeneration int64 `json:"templateGeneration,omitempty" protobuf:"varint,4,opt,name=templateGeneration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DaemonSetTemplateGenerationKey is the key of the labels that is added
|
||||||
|
// to daemon set pods to distinguish between old and new pod templates
|
||||||
|
// during DaemonSet template update.
|
||||||
|
DaemonSetTemplateGenerationKey string = "pod-template-generation"
|
||||||
|
)
|
||||||
|
|
||||||
// DaemonSetList is a collection of daemon sets.
|
// DaemonSetList is a collection of daemon sets.
|
||||||
type DaemonSetList struct {
|
type DaemonSetList struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
@ -110,6 +110,9 @@ func validateDaemonSetStatus(status *extensions.DaemonSetStatus, fldPath *field.
|
|||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.DesiredNumberScheduled), fldPath.Child("desiredNumberScheduled"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.DesiredNumberScheduled), fldPath.Child("desiredNumberScheduled"))...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberReady), fldPath.Child("numberReady"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberReady), fldPath.Child("numberReady"))...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...)
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedNumberScheduled), fldPath.Child("updatedNumberScheduled"))...)
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberAvailable), fldPath.Child("numberAvailable"))...)
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberUnavailable), fldPath.Child("numberUnavailable"))...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +144,39 @@ func ValidateDaemonSetSpec(spec *extensions.DaemonSetSpec, fldPath *field.Path)
|
|||||||
if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
|
if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
|
||||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
|
allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
|
||||||
}
|
}
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
|
||||||
|
|
||||||
|
allErrs = append(allErrs, ValidateDaemonSetUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateRollingUpdateDaemonSet(rollingUpdate *extensions.RollingUpdateDaemonSet, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
|
||||||
|
if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 {
|
||||||
|
// MaxUnavailable cannot be 0.
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "cannot be 0"))
|
||||||
|
}
|
||||||
|
// Validate that MaxUnavailable is not more than 100%.
|
||||||
|
allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateDaemonSetUpdateStrategy(strategy *extensions.DaemonSetUpdateStrategy, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
switch strategy.Type {
|
||||||
|
case extensions.OnDeleteDaemonSetStrategyType:
|
||||||
|
case extensions.RollingUpdateDaemonSetStrategyType:
|
||||||
|
// Make sure RollingUpdate field isn't nil.
|
||||||
|
if strategy.RollingUpdate == nil {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), ""))
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, ValidateRollingUpdateDaemonSet(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...)
|
||||||
|
default:
|
||||||
|
validValues := []string{string(extensions.RollingUpdateDaemonSetStrategyType), string(extensions.OnDeleteDaemonSetStrategyType)}
|
||||||
|
allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues))
|
||||||
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
NumberMisscheduled: 2,
|
NumberMisscheduled: 2,
|
||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -55,6 +58,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
NumberMisscheduled: 1,
|
NumberMisscheduled: 1,
|
||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -81,6 +87,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -95,6 +104,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: -3,
|
DesiredNumberScheduled: -3,
|
||||||
NumberReady: -1,
|
NumberReady: -1,
|
||||||
ObservedGeneration: -3,
|
ObservedGeneration: -3,
|
||||||
|
UpdatedNumberScheduled: -1,
|
||||||
|
NumberAvailable: -1,
|
||||||
|
NumberUnavailable: -2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -111,6 +123,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -125,6 +140,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -141,6 +159,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -155,6 +176,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -171,6 +195,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -185,6 +212,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: -3,
|
DesiredNumberScheduled: -3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -201,6 +231,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -215,6 +248,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: -1,
|
NumberReady: -1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -231,6 +267,9 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: 3,
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -245,6 +284,117 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
|
|||||||
DesiredNumberScheduled: 3,
|
DesiredNumberScheduled: 3,
|
||||||
NumberReady: 1,
|
NumberReady: 1,
|
||||||
ObservedGeneration: -3,
|
ObservedGeneration: -3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"negative UpdatedNumberScheduled": {
|
||||||
|
old: extensions.DaemonSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "abc",
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
ResourceVersion: "10",
|
||||||
|
},
|
||||||
|
Status: extensions.DaemonSetStatus{
|
||||||
|
CurrentNumberScheduled: 1,
|
||||||
|
NumberMisscheduled: 2,
|
||||||
|
DesiredNumberScheduled: 3,
|
||||||
|
NumberReady: 1,
|
||||||
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: extensions.DaemonSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "abc",
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
ResourceVersion: "10",
|
||||||
|
},
|
||||||
|
Status: extensions.DaemonSetStatus{
|
||||||
|
CurrentNumberScheduled: 1,
|
||||||
|
NumberMisscheduled: 1,
|
||||||
|
DesiredNumberScheduled: 3,
|
||||||
|
NumberReady: 1,
|
||||||
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: -1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"negative NumberAvailable": {
|
||||||
|
old: extensions.DaemonSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "abc",
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
ResourceVersion: "10",
|
||||||
|
},
|
||||||
|
Status: extensions.DaemonSetStatus{
|
||||||
|
CurrentNumberScheduled: 1,
|
||||||
|
NumberMisscheduled: 2,
|
||||||
|
DesiredNumberScheduled: 3,
|
||||||
|
NumberReady: 1,
|
||||||
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: extensions.DaemonSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "abc",
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
ResourceVersion: "10",
|
||||||
|
},
|
||||||
|
Status: extensions.DaemonSetStatus{
|
||||||
|
CurrentNumberScheduled: 1,
|
||||||
|
NumberMisscheduled: 1,
|
||||||
|
DesiredNumberScheduled: 3,
|
||||||
|
NumberReady: 1,
|
||||||
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: -1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"negative NumberUnavailable": {
|
||||||
|
old: extensions.DaemonSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "abc",
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
ResourceVersion: "10",
|
||||||
|
},
|
||||||
|
Status: extensions.DaemonSetStatus{
|
||||||
|
CurrentNumberScheduled: 1,
|
||||||
|
NumberMisscheduled: 2,
|
||||||
|
DesiredNumberScheduled: 3,
|
||||||
|
NumberReady: 1,
|
||||||
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: extensions.DaemonSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "abc",
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
ResourceVersion: "10",
|
||||||
|
},
|
||||||
|
Status: extensions.DaemonSetStatus{
|
||||||
|
CurrentNumberScheduled: 1,
|
||||||
|
NumberMisscheduled: 1,
|
||||||
|
DesiredNumberScheduled: 3,
|
||||||
|
NumberReady: 1,
|
||||||
|
ObservedGeneration: 3,
|
||||||
|
UpdatedNumberScheduled: 1,
|
||||||
|
NumberAvailable: 1,
|
||||||
|
NumberUnavailable: -2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -349,6 +499,9 @@ func TestValidateDaemonSetUpdate(t *testing.T) {
|
|||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
||||||
Template: validPodTemplateAbc.Template,
|
Template: validPodTemplateAbc.Template,
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -356,6 +509,9 @@ func TestValidateDaemonSetUpdate(t *testing.T) {
|
|||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
||||||
Template: validPodTemplateAbc.Template,
|
Template: validPodTemplateAbc.Template,
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -365,6 +521,9 @@ func TestValidateDaemonSetUpdate(t *testing.T) {
|
|||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
||||||
Template: validPodTemplateAbc.Template,
|
Template: validPodTemplateAbc.Template,
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -372,6 +531,9 @@ func TestValidateDaemonSetUpdate(t *testing.T) {
|
|||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: validSelector2},
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector2},
|
||||||
Template: validPodTemplateAbc2.Template,
|
Template: validPodTemplateAbc2.Template,
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -381,6 +543,9 @@ func TestValidateDaemonSetUpdate(t *testing.T) {
|
|||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
||||||
Template: validPodTemplateAbc.Template,
|
Template: validPodTemplateAbc.Template,
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: extensions.DaemonSet{
|
update: extensions.DaemonSet{
|
||||||
@ -388,6 +553,9 @@ func TestValidateDaemonSetUpdate(t *testing.T) {
|
|||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
||||||
Template: validPodTemplateNodeSelector.Template,
|
Template: validPodTemplateNodeSelector.Template,
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -536,6 +704,9 @@ func TestValidateDaemonSet(t *testing.T) {
|
|||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
||||||
Template: validPodTemplate.Template,
|
Template: validPodTemplate.Template,
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -543,6 +714,9 @@ func TestValidateDaemonSet(t *testing.T) {
|
|||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
||||||
Template: validPodTemplate.Template,
|
Template: validPodTemplate.Template,
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ go_library(
|
|||||||
srcs = [
|
srcs = [
|
||||||
"daemoncontroller.go",
|
"daemoncontroller.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
|
"update.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
@ -26,6 +27,8 @@ go_library(
|
|||||||
"//pkg/client/listers/core/v1:go_default_library",
|
"//pkg/client/listers/core/v1:go_default_library",
|
||||||
"//pkg/client/listers/extensions/v1beta1:go_default_library",
|
"//pkg/client/listers/extensions/v1beta1:go_default_library",
|
||||||
"//pkg/controller:go_default_library",
|
"//pkg/controller:go_default_library",
|
||||||
|
"//pkg/controller/daemon/util:go_default_library",
|
||||||
|
"//pkg/controller/deployment/util:go_default_library",
|
||||||
"//pkg/util/metrics:go_default_library",
|
"//pkg/util/metrics:go_default_library",
|
||||||
"//plugin/pkg/scheduler/algorithm:go_default_library",
|
"//plugin/pkg/scheduler/algorithm:go_default_library",
|
||||||
"//plugin/pkg/scheduler/algorithm/predicates:go_default_library",
|
"//plugin/pkg/scheduler/algorithm/predicates:go_default_library",
|
||||||
@ -35,6 +38,7 @@ go_library(
|
|||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/labels",
|
"//vendor:k8s.io/apimachinery/pkg/labels",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/util/intstr",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||||
"//vendor:k8s.io/client-go/kubernetes/typed/core/v1",
|
"//vendor:k8s.io/client-go/kubernetes/typed/core/v1",
|
||||||
@ -47,7 +51,10 @@ go_library(
|
|||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["daemoncontroller_test.go"],
|
srcs = [
|
||||||
|
"daemoncontroller_test.go",
|
||||||
|
"update_test.go",
|
||||||
|
],
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
@ -78,6 +85,9 @@ filegroup(
|
|||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [":package-srcs"],
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//pkg/controller/daemon/util:all-srcs",
|
||||||
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
)
|
)
|
||||||
|
@ -44,6 +44,7 @@ import (
|
|||||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/v1"
|
corelisters "k8s.io/kubernetes/pkg/client/listers/core/v1"
|
||||||
extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/v1beta1"
|
extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/v1beta1"
|
||||||
"k8s.io/kubernetes/pkg/controller"
|
"k8s.io/kubernetes/pkg/controller"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/daemon/util"
|
||||||
"k8s.io/kubernetes/pkg/util/metrics"
|
"k8s.io/kubernetes/pkg/util/metrics"
|
||||||
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
||||||
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates"
|
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates"
|
||||||
@ -255,6 +256,17 @@ func (dsc *DaemonSetsController) enqueueDaemonSet(ds *extensions.DaemonSet) {
|
|||||||
dsc.queue.Add(key)
|
dsc.queue.Add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dsc *DaemonSetsController) enqueueDaemonSetAfter(obj interface{}, after time.Duration) {
|
||||||
|
key, err := controller.KeyFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %+v: %v", obj, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle overlapping controllers better. See comment in ReplicationManager.
|
||||||
|
dsc.queue.AddAfter(key, after)
|
||||||
|
}
|
||||||
|
|
||||||
func (dsc *DaemonSetsController) getPodDaemonSet(pod *v1.Pod) *extensions.DaemonSet {
|
func (dsc *DaemonSetsController) getPodDaemonSet(pod *v1.Pod) *extensions.DaemonSet {
|
||||||
// look up in the cache, if cached and the cache is valid, just return cached value
|
// look up in the cache, if cached and the cache is valid, just return cached value
|
||||||
if obj, cached := dsc.lookupCache.GetMatchingObject(pod); cached {
|
if obj, cached := dsc.lookupCache.GetMatchingObject(pod); cached {
|
||||||
@ -342,8 +354,14 @@ func (dsc *DaemonSetsController) updatePod(old, cur interface{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
glog.V(4).Infof("Pod %s updated.", curPod.Name)
|
glog.V(4).Infof("Pod %s updated.", curPod.Name)
|
||||||
|
changedToReady := !v1.IsPodReady(oldPod) && v1.IsPodReady(curPod)
|
||||||
if curDS := dsc.getPodDaemonSet(curPod); curDS != nil {
|
if curDS := dsc.getPodDaemonSet(curPod); curDS != nil {
|
||||||
dsc.enqueueDaemonSet(curDS)
|
dsc.enqueueDaemonSet(curDS)
|
||||||
|
|
||||||
|
// See https://github.com/kubernetes/kubernetes/pull/38076 for more details
|
||||||
|
if changedToReady && curDS.Spec.MinReadySeconds > 0 {
|
||||||
|
dsc.enqueueDaemonSetAfter(curDS, time.Duration(curDS.Spec.MinReadySeconds)*time.Second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If the labels have not changed, then the daemon set responsible for
|
// If the labels have not changed, then the daemon set responsible for
|
||||||
// the pod is the same as it was before. In that case we have enqueued the daemon
|
// the pod is the same as it was before. In that case we have enqueued the daemon
|
||||||
@ -521,11 +539,23 @@ func (dsc *DaemonSetsController) manage(ds *extensions.DaemonSet) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
errors := dsc.syncNodes(ds, podsToDelete, nodesNeedingDaemonPods)
|
||||||
|
|
||||||
|
// Throw an error when the daemon pods fail, to use ratelimiter to prevent kill-recreate hot loop
|
||||||
|
if failedPodsObserved > 0 {
|
||||||
|
errors = append(errors, fmt.Errorf("deleted %d failed pods of DaemonSet %s/%s", failedPodsObserved, ds.Namespace, ds.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilerrors.NewAggregate(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncNodes deletes given pods and creates new daemon set pods on the given node
|
||||||
|
// returns slice with erros if any
|
||||||
|
func (dsc *DaemonSetsController) syncNodes(ds *extensions.DaemonSet, podsToDelete, nodesNeedingDaemonPods []string) []error {
|
||||||
// We need to set expectations before creating/deleting pods to avoid race conditions.
|
// We need to set expectations before creating/deleting pods to avoid race conditions.
|
||||||
dsKey, err := controller.KeyFunc(ds)
|
dsKey, err := controller.KeyFunc(ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't get key for object %#v: %v", ds, err)
|
return []error{fmt.Errorf("couldn't get key for object %#v: %v", ds, err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
createDiff := len(nodesNeedingDaemonPods)
|
createDiff := len(nodesNeedingDaemonPods)
|
||||||
@ -546,10 +576,11 @@ func (dsc *DaemonSetsController) manage(ds *extensions.DaemonSet) error {
|
|||||||
glog.V(4).Infof("Nodes needing daemon pods for daemon set %s: %+v, creating %d", ds.Name, nodesNeedingDaemonPods, createDiff)
|
glog.V(4).Infof("Nodes needing daemon pods for daemon set %s: %+v, creating %d", ds.Name, nodesNeedingDaemonPods, createDiff)
|
||||||
createWait := sync.WaitGroup{}
|
createWait := sync.WaitGroup{}
|
||||||
createWait.Add(createDiff)
|
createWait.Add(createDiff)
|
||||||
|
template := util.GetPodTemplateWithGeneration(ds.Spec.Template, ds.TemplateGeneration)
|
||||||
for i := 0; i < createDiff; i++ {
|
for i := 0; i < createDiff; i++ {
|
||||||
go func(ix int) {
|
go func(ix int) {
|
||||||
defer createWait.Done()
|
defer createWait.Done()
|
||||||
if err := dsc.podControl.CreatePodsOnNode(nodesNeedingDaemonPods[ix], ds.Namespace, &ds.Spec.Template, ds); err != nil {
|
if err := dsc.podControl.CreatePodsOnNode(nodesNeedingDaemonPods[ix], ds.Namespace, &template, ds); err != nil {
|
||||||
glog.V(2).Infof("Failed creation, decrementing expectations for set %q/%q", ds.Namespace, ds.Name)
|
glog.V(2).Infof("Failed creation, decrementing expectations for set %q/%q", ds.Namespace, ds.Name)
|
||||||
dsc.expectations.CreationObserved(dsKey)
|
dsc.expectations.CreationObserved(dsKey)
|
||||||
errCh <- err
|
errCh <- err
|
||||||
@ -581,18 +612,17 @@ func (dsc *DaemonSetsController) manage(ds *extensions.DaemonSet) error {
|
|||||||
for err := range errCh {
|
for err := range errCh {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
// Throw an error when the daemon pods fail, to use ratelimiter to prevent kill-recreate hot loop
|
return errors
|
||||||
if failedPodsObserved > 0 {
|
|
||||||
errors = append(errors, fmt.Errorf("deleted %d failed pods of DaemonSet %s/%s", failedPodsObserved, ds.Namespace, ds.Name))
|
|
||||||
}
|
|
||||||
return utilerrors.NewAggregate(errors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func storeDaemonSetStatus(dsClient unversionedextensions.DaemonSetInterface, ds *extensions.DaemonSet, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady int) error {
|
func storeDaemonSetStatus(dsClient unversionedextensions.DaemonSetInterface, ds *extensions.DaemonSet, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady, updatedNumberScheduled, numberAvailable, numberUnavailable int) error {
|
||||||
if int(ds.Status.DesiredNumberScheduled) == desiredNumberScheduled &&
|
if int(ds.Status.DesiredNumberScheduled) == desiredNumberScheduled &&
|
||||||
int(ds.Status.CurrentNumberScheduled) == currentNumberScheduled &&
|
int(ds.Status.CurrentNumberScheduled) == currentNumberScheduled &&
|
||||||
int(ds.Status.NumberMisscheduled) == numberMisscheduled &&
|
int(ds.Status.NumberMisscheduled) == numberMisscheduled &&
|
||||||
int(ds.Status.NumberReady) == numberReady &&
|
int(ds.Status.NumberReady) == numberReady &&
|
||||||
|
int(ds.Status.UpdatedNumberScheduled) == updatedNumberScheduled &&
|
||||||
|
int(ds.Status.NumberAvailable) == numberAvailable &&
|
||||||
|
int(ds.Status.NumberUnavailable) == numberUnavailable &&
|
||||||
ds.Status.ObservedGeneration >= ds.Generation {
|
ds.Status.ObservedGeneration >= ds.Generation {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -611,6 +641,9 @@ func storeDaemonSetStatus(dsClient unversionedextensions.DaemonSetInterface, ds
|
|||||||
toUpdate.Status.CurrentNumberScheduled = int32(currentNumberScheduled)
|
toUpdate.Status.CurrentNumberScheduled = int32(currentNumberScheduled)
|
||||||
toUpdate.Status.NumberMisscheduled = int32(numberMisscheduled)
|
toUpdate.Status.NumberMisscheduled = int32(numberMisscheduled)
|
||||||
toUpdate.Status.NumberReady = int32(numberReady)
|
toUpdate.Status.NumberReady = int32(numberReady)
|
||||||
|
toUpdate.Status.UpdatedNumberScheduled = int32(updatedNumberScheduled)
|
||||||
|
toUpdate.Status.NumberAvailable = int32(numberAvailable)
|
||||||
|
toUpdate.Status.NumberUnavailable = int32(numberUnavailable)
|
||||||
|
|
||||||
if _, updateErr = dsClient.UpdateStatus(toUpdate); updateErr == nil {
|
if _, updateErr = dsClient.UpdateStatus(toUpdate); updateErr == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -638,7 +671,7 @@ func (dsc *DaemonSetsController) updateDaemonSetStatus(ds *extensions.DaemonSet)
|
|||||||
return fmt.Errorf("couldn't get list of nodes when updating daemon set %#v: %v", ds, err)
|
return fmt.Errorf("couldn't get list of nodes when updating daemon set %#v: %v", ds, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady int
|
var desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady, updatedNumberScheduled, numberAvailable int
|
||||||
for i := range nodeList {
|
for i := range nodeList {
|
||||||
node := nodeList[i]
|
node := nodeList[i]
|
||||||
wantToRun, _, _, err := dsc.nodeShouldRunDaemonPod(node, ds)
|
wantToRun, _, _, err := dsc.nodeShouldRunDaemonPod(node, ds)
|
||||||
@ -652,11 +685,18 @@ func (dsc *DaemonSetsController) updateDaemonSetStatus(ds *extensions.DaemonSet)
|
|||||||
desiredNumberScheduled++
|
desiredNumberScheduled++
|
||||||
if scheduled {
|
if scheduled {
|
||||||
currentNumberScheduled++
|
currentNumberScheduled++
|
||||||
// Sort the daemon pods by creation time, so the the oldest is first.
|
// Sort the daemon pods by creation time, so that the oldest is first.
|
||||||
daemonPods, _ := nodeToDaemonPods[node.Name]
|
daemonPods, _ := nodeToDaemonPods[node.Name]
|
||||||
sort.Sort(podByCreationTimestamp(daemonPods))
|
sort.Sort(podByCreationTimestamp(daemonPods))
|
||||||
if v1.IsPodReady(daemonPods[0]) {
|
pod := daemonPods[0]
|
||||||
|
if v1.IsPodReady(pod) {
|
||||||
numberReady++
|
numberReady++
|
||||||
|
if v1.IsPodAvailable(pod, ds.Spec.MinReadySeconds, metav1.Now()) {
|
||||||
|
numberAvailable++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if util.IsPodUpdated(ds.TemplateGeneration, pod) {
|
||||||
|
updatedNumberScheduled++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -665,8 +705,9 @@ func (dsc *DaemonSetsController) updateDaemonSetStatus(ds *extensions.DaemonSet)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
numberUnavailable := desiredNumberScheduled - numberAvailable
|
||||||
|
|
||||||
err = storeDaemonSetStatus(dsc.kubeClient.Extensions().DaemonSets(ds.Namespace), ds, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady)
|
err = storeDaemonSetStatus(dsc.kubeClient.Extensions().DaemonSets(ds.Namespace), ds, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled, numberReady, updatedNumberScheduled, numberAvailable, numberUnavailable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error storing status for daemon set %#v: %v", ds, err)
|
return fmt.Errorf("error storing status for daemon set %#v: %v", ds, err)
|
||||||
}
|
}
|
||||||
@ -714,6 +755,17 @@ func (dsc *DaemonSetsController) syncDaemonSet(key string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dsNeedsSync = dsc.expectations.SatisfiedExpectations(dsKey)
|
||||||
|
if dsNeedsSync && ds.DeletionTimestamp == nil {
|
||||||
|
switch ds.Spec.UpdateStrategy.Type {
|
||||||
|
case extensions.RollingUpdateDaemonSetStrategyType:
|
||||||
|
err = dsc.rollingUpdate(ds)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dsc.updateDaemonSetStatus(ds)
|
return dsc.updateDaemonSetStatus(ds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,11 +18,13 @@ package daemon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
core "k8s.io/client-go/testing"
|
core "k8s.io/client-go/testing"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
@ -166,6 +168,63 @@ func addFailedPods(podStore cache.Store, nodeName string, label map[string]strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakePodControl struct {
|
||||||
|
sync.Mutex
|
||||||
|
*controller.FakePodControl
|
||||||
|
podStore cache.Store
|
||||||
|
podIDMap map[string]*v1.Pod
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakePodControl() *fakePodControl {
|
||||||
|
podIDMap := make(map[string]*v1.Pod)
|
||||||
|
return &fakePodControl{
|
||||||
|
FakePodControl: &controller.FakePodControl{},
|
||||||
|
podIDMap: podIDMap}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakePodControl) CreatePodsOnNode(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object) error {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
if err := f.FakePodControl.CreatePodsOnNode(nodeName, namespace, template, object); err != nil {
|
||||||
|
return fmt.Errorf("failed to create pod on node %q", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
pod := &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: template.Labels,
|
||||||
|
Namespace: namespace,
|
||||||
|
GenerateName: fmt.Sprintf("%s-", nodeName),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := api.Scheme.Convert(&template.Spec, &pod.Spec, nil); err != nil {
|
||||||
|
return fmt.Errorf("unable to convert pod template: %v", err)
|
||||||
|
}
|
||||||
|
if len(nodeName) != 0 {
|
||||||
|
pod.Spec.NodeName = nodeName
|
||||||
|
}
|
||||||
|
pod.Name = names.SimpleNameGenerator.GenerateName(fmt.Sprintf("%s-", nodeName))
|
||||||
|
|
||||||
|
f.podStore.Update(pod)
|
||||||
|
f.podIDMap[pod.Name] = pod
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakePodControl) DeletePod(namespace string, podID string, object runtime.Object) error {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
if err := f.FakePodControl.DeletePod(namespace, podID, object); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete pod %q", podID)
|
||||||
|
}
|
||||||
|
pod, ok := f.podIDMap[podID]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("pod %q does not exist", podID)
|
||||||
|
}
|
||||||
|
f.podStore.Delete(pod)
|
||||||
|
delete(f.podIDMap, podID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type daemonSetsController struct {
|
type daemonSetsController struct {
|
||||||
*DaemonSetsController
|
*DaemonSetsController
|
||||||
|
|
||||||
@ -174,7 +233,7 @@ type daemonSetsController struct {
|
|||||||
nodeStore cache.Store
|
nodeStore cache.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestController(initialObjects ...runtime.Object) (*daemonSetsController, *controller.FakePodControl, *fake.Clientset) {
|
func newTestController(initialObjects ...runtime.Object) (*daemonSetsController, *fakePodControl, *fake.Clientset) {
|
||||||
clientset := fake.NewSimpleClientset(initialObjects...)
|
clientset := fake.NewSimpleClientset(initialObjects...)
|
||||||
informerFactory := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc())
|
informerFactory := informers.NewSharedInformerFactory(clientset, controller.NoResyncPeriodFunc())
|
||||||
|
|
||||||
@ -190,8 +249,9 @@ func newTestController(initialObjects ...runtime.Object) (*daemonSetsController,
|
|||||||
manager.podStoreSynced = alwaysReady
|
manager.podStoreSynced = alwaysReady
|
||||||
manager.nodeStoreSynced = alwaysReady
|
manager.nodeStoreSynced = alwaysReady
|
||||||
manager.dsStoreSynced = alwaysReady
|
manager.dsStoreSynced = alwaysReady
|
||||||
podControl := &controller.FakePodControl{}
|
podControl := newFakePodControl()
|
||||||
manager.podControl = podControl
|
manager.podControl = podControl
|
||||||
|
podControl.podStore = informerFactory.Core().V1().Pods().Informer().GetStore()
|
||||||
|
|
||||||
return &daemonSetsController{
|
return &daemonSetsController{
|
||||||
manager,
|
manager,
|
||||||
@ -201,7 +261,7 @@ func newTestController(initialObjects ...runtime.Object) (*daemonSetsController,
|
|||||||
}, podControl, clientset
|
}, podControl, clientset
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSyncDaemonSets(t *testing.T, fakePodControl *controller.FakePodControl, expectedCreates, expectedDeletes int) {
|
func validateSyncDaemonSets(t *testing.T, fakePodControl *fakePodControl, expectedCreates, expectedDeletes int) {
|
||||||
if len(fakePodControl.Templates) != expectedCreates {
|
if len(fakePodControl.Templates) != expectedCreates {
|
||||||
t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", expectedCreates, len(fakePodControl.Templates))
|
t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", expectedCreates, len(fakePodControl.Templates))
|
||||||
}
|
}
|
||||||
@ -210,7 +270,7 @@ func validateSyncDaemonSets(t *testing.T, fakePodControl *controller.FakePodCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncAndValidateDaemonSets(t *testing.T, manager *daemonSetsController, ds *extensions.DaemonSet, podControl *controller.FakePodControl, expectedCreates, expectedDeletes int) {
|
func syncAndValidateDaemonSets(t *testing.T, manager *daemonSetsController, ds *extensions.DaemonSet, podControl *fakePodControl, expectedCreates, expectedDeletes int) {
|
||||||
key, err := controller.KeyFunc(ds)
|
key, err := controller.KeyFunc(ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Could not get key for daemon.")
|
t.Errorf("Could not get key for daemon.")
|
||||||
@ -219,6 +279,18 @@ func syncAndValidateDaemonSets(t *testing.T, manager *daemonSetsController, ds *
|
|||||||
validateSyncDaemonSets(t, podControl, expectedCreates, expectedDeletes)
|
validateSyncDaemonSets(t, podControl, expectedCreates, expectedDeletes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clearExpectations copies the FakePodControl to PodStore and clears the create and delete expectations.
|
||||||
|
func clearExpectations(t *testing.T, manager *daemonSetsController, ds *extensions.DaemonSet, fakePodControl *fakePodControl) {
|
||||||
|
fakePodControl.Clear()
|
||||||
|
|
||||||
|
key, err := controller.KeyFunc(ds)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Could not get key for daemon.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.expectations.DeleteExpectations(key)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeleteFinalStateUnknown(t *testing.T) {
|
func TestDeleteFinalStateUnknown(t *testing.T) {
|
||||||
manager, _, _ := newTestController()
|
manager, _, _ := newTestController()
|
||||||
addNodes(manager.nodeStore, 0, 1, nil)
|
addNodes(manager.nodeStore, 0, 1, nil)
|
||||||
@ -231,6 +303,15 @@ func TestDeleteFinalStateUnknown(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func markPodsReady(store cache.Store) {
|
||||||
|
// mark pods as ready
|
||||||
|
for _, obj := range store.List() {
|
||||||
|
pod := obj.(*v1.Pod)
|
||||||
|
condition := v1.PodCondition{Type: v1.PodReady, Status: v1.ConditionTrue}
|
||||||
|
v1.UpdatePodCondition(&pod.Status, &condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonSets without node selectors should launch pods on every node.
|
// DaemonSets without node selectors should launch pods on every node.
|
||||||
func TestSimpleDaemonSetLaunchesPods(t *testing.T) {
|
func TestSimpleDaemonSetLaunchesPods(t *testing.T) {
|
||||||
manager, podControl, _ := newTestController()
|
manager, podControl, _ := newTestController()
|
||||||
@ -927,3 +1008,114 @@ func TestNodeShouldRunDaemonPod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDaemonSetUpdatesPods(t *testing.T) {
|
||||||
|
manager, podControl, _ := newTestController()
|
||||||
|
maxUnavailable := 2
|
||||||
|
addNodes(manager.nodeStore, 0, 5, nil)
|
||||||
|
ds := newDaemonSet("foo")
|
||||||
|
manager.dsStore.Add(ds)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0)
|
||||||
|
markPodsReady(podControl.podStore)
|
||||||
|
|
||||||
|
ds.Spec.Template.Spec.Containers[0].Image = "foo2/bar2"
|
||||||
|
ds.Spec.UpdateStrategy.Type = extensions.RollingUpdateDaemonSetStrategyType
|
||||||
|
intStr := intstr.FromInt(maxUnavailable)
|
||||||
|
ds.Spec.UpdateStrategy.RollingUpdate = &extensions.RollingUpdateDaemonSet{MaxUnavailable: &intStr}
|
||||||
|
ds.TemplateGeneration++
|
||||||
|
manager.dsStore.Update(ds)
|
||||||
|
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, maxUnavailable)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, maxUnavailable, 0)
|
||||||
|
markPodsReady(podControl.podStore)
|
||||||
|
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, maxUnavailable)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, maxUnavailable, 0)
|
||||||
|
markPodsReady(podControl.podStore)
|
||||||
|
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 1)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0)
|
||||||
|
markPodsReady(podControl.podStore)
|
||||||
|
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDaemonSetUpdatesWhenNewPosIsNotReady(t *testing.T) {
|
||||||
|
manager, podControl, _ := newTestController()
|
||||||
|
maxUnavailable := 3
|
||||||
|
addNodes(manager.nodeStore, 0, 5, nil)
|
||||||
|
ds := newDaemonSet("foo")
|
||||||
|
manager.dsStore.Add(ds)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0)
|
||||||
|
markPodsReady(podControl.podStore)
|
||||||
|
|
||||||
|
ds.Spec.Template.Spec.Containers[0].Image = "foo2/bar2"
|
||||||
|
ds.Spec.UpdateStrategy.Type = extensions.RollingUpdateDaemonSetStrategyType
|
||||||
|
intStr := intstr.FromInt(maxUnavailable)
|
||||||
|
ds.Spec.UpdateStrategy.RollingUpdate = &extensions.RollingUpdateDaemonSet{MaxUnavailable: &intStr}
|
||||||
|
ds.TemplateGeneration++
|
||||||
|
manager.dsStore.Update(ds)
|
||||||
|
|
||||||
|
// new pods are not ready numUnavailable == maxUnavailable
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, maxUnavailable)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, maxUnavailable, 0)
|
||||||
|
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDaemonSetUpdatesAllOldPodsNotReady(t *testing.T) {
|
||||||
|
manager, podControl, _ := newTestController()
|
||||||
|
maxUnavailable := 3
|
||||||
|
addNodes(manager.nodeStore, 0, 5, nil)
|
||||||
|
ds := newDaemonSet("foo")
|
||||||
|
manager.dsStore.Add(ds)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0)
|
||||||
|
|
||||||
|
ds.Spec.Template.Spec.Containers[0].Image = "foo2/bar2"
|
||||||
|
ds.Spec.UpdateStrategy.Type = extensions.RollingUpdateDaemonSetStrategyType
|
||||||
|
intStr := intstr.FromInt(maxUnavailable)
|
||||||
|
ds.Spec.UpdateStrategy.RollingUpdate = &extensions.RollingUpdateDaemonSet{MaxUnavailable: &intStr}
|
||||||
|
ds.TemplateGeneration++
|
||||||
|
manager.dsStore.Update(ds)
|
||||||
|
|
||||||
|
// all old pods are unavailable so should be removed
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 5)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0)
|
||||||
|
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDaemonSetUpdatesNoTemplateChanged(t *testing.T) {
|
||||||
|
manager, podControl, _ := newTestController()
|
||||||
|
maxUnavailable := 3
|
||||||
|
addNodes(manager.nodeStore, 0, 5, nil)
|
||||||
|
ds := newDaemonSet("foo")
|
||||||
|
manager.dsStore.Add(ds)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0)
|
||||||
|
|
||||||
|
ds.Spec.UpdateStrategy.Type = extensions.RollingUpdateDaemonSetStrategyType
|
||||||
|
intStr := intstr.FromInt(maxUnavailable)
|
||||||
|
ds.Spec.UpdateStrategy.RollingUpdate = &extensions.RollingUpdateDaemonSet{MaxUnavailable: &intStr}
|
||||||
|
manager.dsStore.Update(ds)
|
||||||
|
|
||||||
|
// template is not changed no pod should be removed
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0)
|
||||||
|
clearExpectations(t, manager, ds, podControl)
|
||||||
|
}
|
||||||
|
137
pkg/controller/daemon/update.go
Normal file
137
pkg/controller/daemon/update.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
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 daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/daemon/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rollingUpdate deletes old daemon set pods making sure that no more than
|
||||||
|
// ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable pods are unavailable
|
||||||
|
func (dsc *DaemonSetsController) rollingUpdate(ds *extensions.DaemonSet) error {
|
||||||
|
newPods, oldPods, err := dsc.getAllDaemonSetPods(ds)
|
||||||
|
allPods := append(oldPods, newPods...)
|
||||||
|
|
||||||
|
maxUnavailable, numUnavailable, err := dsc.getUnavailableNumbers(ds, allPods)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Couldn't get unavailable numbers: %v", err)
|
||||||
|
}
|
||||||
|
oldAvailablePods, oldUnavailablePods := util.SplitByAvailablePods(ds.Spec.MinReadySeconds, oldPods)
|
||||||
|
|
||||||
|
// for oldPods delete all not running pods
|
||||||
|
var podsToDelete []string
|
||||||
|
glog.V(4).Infof("Marking all unavailable old pods for deletion")
|
||||||
|
for _, pod := range oldUnavailablePods {
|
||||||
|
// Skip terminating pods. We won't delete them again
|
||||||
|
if pod.DeletionTimestamp != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
glog.V(4).Infof("Marking pod %s/%s for deletion", ds.Name, pod.Name)
|
||||||
|
podsToDelete = append(podsToDelete, pod.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(4).Infof("Marking old pods for deletion")
|
||||||
|
for _, pod := range oldAvailablePods {
|
||||||
|
if numUnavailable >= maxUnavailable {
|
||||||
|
glog.V(4).Infof("Number of unavailable DaemonSet pods: %d, is equal to or exceeds allowed maximum: %d", numUnavailable, maxUnavailable)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
glog.V(4).Infof("Marking pod %s/%s for deletion", ds.Name, pod.Name)
|
||||||
|
podsToDelete = append(podsToDelete, pod.Name)
|
||||||
|
numUnavailable++
|
||||||
|
}
|
||||||
|
errors := dsc.syncNodes(ds, podsToDelete, []string{})
|
||||||
|
return utilerrors.NewAggregate(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsc *DaemonSetsController) getAllDaemonSetPods(ds *extensions.DaemonSet) ([]*v1.Pod, []*v1.Pod, error) {
|
||||||
|
var newPods []*v1.Pod
|
||||||
|
var oldPods []*v1.Pod
|
||||||
|
|
||||||
|
selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return newPods, oldPods, err
|
||||||
|
}
|
||||||
|
daemonPods, err := dsc.podLister.Pods(ds.Namespace).List(selector)
|
||||||
|
if err != nil {
|
||||||
|
return newPods, oldPods, fmt.Errorf("Couldn't get list of pods for daemon set %#v: %v", ds, err)
|
||||||
|
}
|
||||||
|
for _, pod := range daemonPods {
|
||||||
|
if util.IsPodUpdated(ds.TemplateGeneration, pod) {
|
||||||
|
newPods = append(newPods, pod)
|
||||||
|
} else {
|
||||||
|
oldPods = append(oldPods, pod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newPods, oldPods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsc *DaemonSetsController) getUnavailableNumbers(ds *extensions.DaemonSet, allPods []*v1.Pod) (int, int, error) {
|
||||||
|
glog.V(4).Infof("Getting unavailable numbers")
|
||||||
|
// TODO: get nodeList once in syncDaemonSet and pass it to other functions
|
||||||
|
nodeList, err := dsc.nodeLister.List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("couldn't get list of nodes during rolling update of daemon set %#v: %v", ds, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeToDaemonPods, err := dsc.getNodesToDaemonPods(ds)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("couldn't get node to daemon pods mapping for daemon set %#v: %v", ds, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var numUnavailable, desiredNumberScheduled int
|
||||||
|
for i := range nodeList {
|
||||||
|
node := nodeList[i]
|
||||||
|
wantToRun, _, _, err := dsc.nodeShouldRunDaemonPod(node, ds)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
if !wantToRun {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
desiredNumberScheduled++
|
||||||
|
daemonPods, exists := nodeToDaemonPods[node.Name]
|
||||||
|
if !exists {
|
||||||
|
numUnavailable++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
available := false
|
||||||
|
for _, pod := range daemonPods {
|
||||||
|
if v1.IsPodAvailable(pod, ds.Spec.MinReadySeconds, metav1.Now()) {
|
||||||
|
available = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !available {
|
||||||
|
numUnavailable++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxUnavailable, err := intstrutil.GetValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, desiredNumberScheduled, true)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, fmt.Errorf("Invalid value for MaxUnavailable: %v", err)
|
||||||
|
}
|
||||||
|
return maxUnavailable, numUnavailable, nil
|
||||||
|
}
|
35
pkg/controller/daemon/update_test.go
Normal file
35
pkg/controller/daemon/update_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
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 daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DaemonSets without node selectors should launch pods on every node.
|
||||||
|
func TestSimpleDaemonSetUpdatesWithRollingUpdate(t *testing.T) {
|
||||||
|
manager, podControl, _ := newTestController()
|
||||||
|
addNodes(manager.nodeStore, 0, 5, nil)
|
||||||
|
ds := newDaemonSet("foo")
|
||||||
|
manager.dsStore.Add(ds)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0)
|
||||||
|
syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0)
|
||||||
|
// change strategy type to RollingUpdate
|
||||||
|
ds.Spec.UpdateStrategy.Type = extensions.RollingUpdateDaemonSetStrategyType
|
||||||
|
}
|
33
pkg/controller/daemon/util/BUILD
Normal file
33
pkg/controller/daemon/util/BUILD
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["daemonset_util.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//pkg/api/v1:go_default_library",
|
||||||
|
"//pkg/apis/extensions/v1beta1:go_default_library",
|
||||||
|
"//pkg/util/labels:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
62
pkg/controller/daemon/util/daemonset_util.go
Normal file
62
pkg/controller/daemon/util/daemonset_util.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||||
|
labelsutil "k8s.io/kubernetes/pkg/util/labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPodTemplateWithHash returns copy of provided template with additional
|
||||||
|
// label which contains hash of provided template
|
||||||
|
func GetPodTemplateWithGeneration(template v1.PodTemplateSpec, generation int64) v1.PodTemplateSpec {
|
||||||
|
obj, _ := api.Scheme.DeepCopy(template)
|
||||||
|
newTemplate := obj.(v1.PodTemplateSpec)
|
||||||
|
templateGenerationStr := fmt.Sprint(generation)
|
||||||
|
newTemplate.ObjectMeta.Labels = labelsutil.CloneAndAddLabel(
|
||||||
|
template.ObjectMeta.Labels,
|
||||||
|
extensions.DaemonSetTemplateGenerationKey,
|
||||||
|
templateGenerationStr,
|
||||||
|
)
|
||||||
|
return newTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPodUpdate checks if pod contains label with provided hash
|
||||||
|
func IsPodUpdated(dsTemplateGeneration int64, pod *v1.Pod) bool {
|
||||||
|
podTemplateGeneration, generationExists := pod.ObjectMeta.Labels[extensions.DaemonSetTemplateGenerationKey]
|
||||||
|
dsTemplateGenerationStr := fmt.Sprint(dsTemplateGeneration)
|
||||||
|
return generationExists && podTemplateGeneration == dsTemplateGenerationStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitByAvailablePods splits provided daemon set pods by availabilty
|
||||||
|
func SplitByAvailablePods(minReadySeconds int32, pods []*v1.Pod) ([]*v1.Pod, []*v1.Pod) {
|
||||||
|
unavailablePods := []*v1.Pod{}
|
||||||
|
availablePods := []*v1.Pod{}
|
||||||
|
for _, pod := range pods {
|
||||||
|
if v1.IsPodAvailable(pod, minReadySeconds, metav1.Now()) {
|
||||||
|
availablePods = append(availablePods, pod)
|
||||||
|
} else {
|
||||||
|
unavailablePods = append(unavailablePods, pod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availablePods, unavailablePods
|
||||||
|
}
|
87
pkg/controller/daemon/util/daemonset_util_test.go
Normal file
87
pkg/controller/daemon/util/daemonset_util_test.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/api/testapi"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newPod(podName string, nodeName string, label map[string]string) *v1.Pod {
|
||||||
|
pod := &v1.Pod{
|
||||||
|
TypeMeta: metav1.TypeMeta{APIVersion: testapi.Extensions.GroupVersion().String()},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: label,
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
NodeName: nodeName,
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Image: "foo/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Name = podName
|
||||||
|
return pod
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsPodUpdated(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
templateGeneration int64
|
||||||
|
pod *v1.Pod
|
||||||
|
isUpdated bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
int64(12345),
|
||||||
|
newPod("pod1", "node1", map[string]string{extensions.DaemonSetTemplateGenerationKey: "12345"}),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
int64(12355),
|
||||||
|
newPod("pod1", "node1", map[string]string{extensions.DaemonSetTemplateGenerationKey: "12345"}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
int64(12355),
|
||||||
|
newPod("pod1", "node1", map[string]string{}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
updated := IsPodUpdated(test.templateGeneration, test.pod)
|
||||||
|
if updated != test.isUpdated {
|
||||||
|
t.Errorf("IsPodUpdated returned wrong value. Expected %t, got %t. TemplateGeneration: %d", test.isUpdated, updated, test.templateGeneration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPodTemplateWithRevision(t *testing.T) {
|
||||||
|
generation := int64(1)
|
||||||
|
podTemplateSpec := v1.PodTemplateSpec{}
|
||||||
|
newPodTemplate := GetPodTemplateWithGeneration(podTemplateSpec, generation)
|
||||||
|
label, exists := newPodTemplate.ObjectMeta.Labels[extensions.DaemonSetTemplateGenerationKey]
|
||||||
|
if !exists || label != fmt.Sprint(generation) {
|
||||||
|
t.Errorf("Error in getting podTemplateSpec with label geneartion. Exists: %t, label: %s", exists, label)
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,8 @@ func GetInternalPodTemplateSpecHash(template api.PodTemplateSpec) uint32 {
|
|||||||
return podTemplateSpecHasher.Sum32()
|
return podTemplateSpecHasher.Sum32()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move it to a better place. It's also used by DaemonSets
|
||||||
|
// Maybe to pkg/api/v1/resource_helpers.go
|
||||||
func GetPodTemplateSpecHashFnv(template v1.PodTemplateSpec) uint32 {
|
func GetPodTemplateSpecHashFnv(template v1.PodTemplateSpec) uint32 {
|
||||||
podTemplateSpecHasher := fnv.New32a()
|
podTemplateSpecHasher := fnv.New32a()
|
||||||
hashutil.DeepHashObject(podTemplateSpecHasher, template)
|
hashutil.DeepHashObject(podTemplateSpecHasher, template)
|
||||||
|
@ -49,6 +49,9 @@ func newValidDaemonSet() *extensions.DaemonSet {
|
|||||||
Namespace: metav1.NamespaceDefault,
|
Namespace: metav1.NamespaceDefault,
|
||||||
},
|
},
|
||||||
Spec: extensions.DaemonSetSpec{
|
Spec: extensions.DaemonSetSpec{
|
||||||
|
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||||
|
Type: extensions.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
|
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
|
||||||
Template: api.PodTemplateSpec{
|
Template: api.PodTemplateSpec{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -53,6 +53,7 @@ func (daemonSetStrategy) PrepareForCreate(ctx genericapirequest.Context, obj run
|
|||||||
daemonSet.Status = extensions.DaemonSetStatus{}
|
daemonSet.Status = extensions.DaemonSetStatus{}
|
||||||
|
|
||||||
daemonSet.Generation = 1
|
daemonSet.Generation = 1
|
||||||
|
daemonSet.TemplateGeneration = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||||
@ -63,6 +64,9 @@ func (daemonSetStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, ol
|
|||||||
// update is not allowed to set status
|
// update is not allowed to set status
|
||||||
newDaemonSet.Status = oldDaemonSet.Status
|
newDaemonSet.Status = oldDaemonSet.Status
|
||||||
|
|
||||||
|
// update is not allowed to set TemplateGeneration
|
||||||
|
newDaemonSet.TemplateGeneration = oldDaemonSet.TemplateGeneration
|
||||||
|
|
||||||
// Any changes to the spec increment the generation number, any changes to the
|
// Any changes to the spec increment the generation number, any changes to the
|
||||||
// status should reflect the generation number of the corresponding object. We push
|
// status should reflect the generation number of the corresponding object. We push
|
||||||
// the burden of managing the status onto the clients because we can't (in general)
|
// the burden of managing the status onto the clients because we can't (in general)
|
||||||
@ -74,6 +78,11 @@ func (daemonSetStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, ol
|
|||||||
//
|
//
|
||||||
// TODO: Any changes to a part of the object that represents desired state (labels,
|
// TODO: Any changes to a part of the object that represents desired state (labels,
|
||||||
// annotations etc) should also increment the generation.
|
// annotations etc) should also increment the generation.
|
||||||
|
if !reflect.DeepEqual(oldDaemonSet.Spec.Template, newDaemonSet.Spec.Template) {
|
||||||
|
newDaemonSet.TemplateGeneration = oldDaemonSet.TemplateGeneration + 1
|
||||||
|
newDaemonSet.Generation = oldDaemonSet.Generation + 1
|
||||||
|
return
|
||||||
|
}
|
||||||
if !reflect.DeepEqual(oldDaemonSet.Spec, newDaemonSet.Spec) {
|
if !reflect.DeepEqual(oldDaemonSet.Spec, newDaemonSet.Spec) {
|
||||||
newDaemonSet.Generation = oldDaemonSet.Generation + 1
|
newDaemonSet.Generation = oldDaemonSet.Generation + 1
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
|
|||||||
f = framework.NewDefaultFramework("daemonsets")
|
f = framework.NewDefaultFramework("daemonsets")
|
||||||
|
|
||||||
image := "gcr.io/google_containers/serve_hostname:v1.4"
|
image := "gcr.io/google_containers/serve_hostname:v1.4"
|
||||||
|
redisImage := "gcr.io/google_containers/redis:e2e"
|
||||||
dsName := "daemon-set"
|
dsName := "daemon-set"
|
||||||
|
|
||||||
var ns string
|
var ns string
|
||||||
@ -231,6 +232,64 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
|
|||||||
err = wait.Poll(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, label))
|
err = wait.Poll(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, label))
|
||||||
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to revive")
|
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to revive")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Should not update pod when spec was updated and update strategy is on delete", func() {
|
||||||
|
label := map[string]string{daemonsetNameLabel: dsName}
|
||||||
|
|
||||||
|
framework.Logf("Creating simple daemon set %s", dsName)
|
||||||
|
_, err := c.Extensions().DaemonSets(ns).Create(newDaemonSet(dsName, image, label))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Check that daemon pods launch on every node of the cluster.")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = wait.Poll(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, label))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start")
|
||||||
|
|
||||||
|
By("Update daemon pods image.")
|
||||||
|
ds, err := c.Extensions().DaemonSets(ns).Get(dsName, metav1.GetOptions{})
|
||||||
|
ds.Spec.Template.Spec.Containers[0].Image = redisImage
|
||||||
|
_, err = c.Extensions().DaemonSets(ns).Update(ds)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Check that demon pods have not set updated image.")
|
||||||
|
err = wait.Poll(dsRetryPeriod, dsRetryTimeout, checkDaemonPodsImage(c, ns, label, image))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Check that daemon pods are still running on every node of the cluster.")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = wait.Poll(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, label))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should update pod when spec was updated and update strategy is RollingUpdate", func() {
|
||||||
|
label := map[string]string{daemonsetNameLabel: dsName}
|
||||||
|
|
||||||
|
framework.Logf("Creating simple daemon set %s", dsName)
|
||||||
|
_, err := c.Extensions().DaemonSets(ns).Create(newDaemonSet(dsName, image, label))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Check that daemon pods launch on every node of the cluster.")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = wait.Poll(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, label))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start")
|
||||||
|
|
||||||
|
By("Update daemon pods image.")
|
||||||
|
ds, err := c.Extensions().DaemonSets(ns).Get(dsName, metav1.GetOptions{})
|
||||||
|
ds.Spec.Template.Spec.Containers[0].Image = redisImage
|
||||||
|
ds.Spec.UpdateStrategy = extensions.DaemonSetUpdateStrategy{Type: extensions.RollingUpdateDaemonSetStrategyType}
|
||||||
|
_, err = c.Extensions().DaemonSets(ns).Update(ds)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Check that demon pods have not set updated image.")
|
||||||
|
err = wait.Poll(dsRetryPeriod, dsRetryTimeout, checkDaemonPodsImage(c, ns, label, redisImage))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Check that daemon pods are still running on every node of the cluster.")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = wait.Poll(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, label))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start")
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
func newDaemonSet(dsName, image string, label map[string]string) *extensions.DaemonSet {
|
func newDaemonSet(dsName, image string, label map[string]string) *extensions.DaemonSet {
|
||||||
@ -389,3 +448,24 @@ func checkDaemonStatus(f *framework.Framework, dsName string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkDaemonPodsImage(c clientset.Interface, ns string, selector map[string]string, image string) func() (bool, error) {
|
||||||
|
return func() (bool, error) {
|
||||||
|
selector := labels.Set(selector).AsSelector()
|
||||||
|
options := metav1.ListOptions{LabelSelector: selector.String()}
|
||||||
|
podList, err := c.Core().Pods(ns).List(options)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
pods := podList.Items
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
podImage := pod.Spec.Containers[0].Image
|
||||||
|
if podImage != image || !v1.IsPodReady(&pod) {
|
||||||
|
framework.Logf("Wrong image for pod: %s. Expected: %s, got: %s. Pod Ready: %t", pod.Name, image, podImage, v1.IsPodReady(&pod))
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user