Commit dbd1503b authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #45924 from janetkuo/daemonset-history

Automatic merge from submit-queue Implement Daemonset history ~Depends on #45867 (the 1st commit, ignore it when reviewing)~ (already merged) Ref https://github.com/kubernetes/community/pull/527/ and https://github.com/kubernetes/community/pull/594 @kubernetes/sig-apps-api-reviews @kubernetes/sig-apps-pr-reviews @erictune @kow3ns @lukaszo @kargakis --- TODOs: - [x] API changes - [x] (maybe) Remove rollback subresource if we decide to do client-side rollback - [x] deployment controller - [x] controller revision - [x] owner ref (claim & adoption) - [x] history reconstruct (put revision number, hash collision avoidance) - [x] de-dup history and relabel pods - [x] compare ds template with history - [x] hash labels (put it in controller revision, pods, and maybe deployment) - [x] clean up old history - [x] Rename status.uniquifier when we reach consensus in #44774 - [x] e2e tests - [x] unit tests - [x] daemoncontroller_test.go - [x] update_test.go - [x] ~(maybe) storage_test.go // if we do server side rollback~ kubectl part is in #46144 --- **Release note**: ```release-note ```
parents be987b01 85ec49c9
...@@ -52564,6 +52564,11 @@ ...@@ -52564,6 +52564,11 @@
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
}, },
"revisionHistoryLimit": {
"description": "The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.",
"type": "integer",
"format": "int32"
},
"selector": { "selector": {
"description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector"
...@@ -52573,7 +52578,7 @@ ...@@ -52573,7 +52578,7 @@
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.PodTemplateSpec" "$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.PodTemplateSpec"
}, },
"templateGeneration": { "templateGeneration": {
"description": "A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.", "description": "DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.",
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
...@@ -52592,6 +52597,11 @@ ...@@ -52592,6 +52597,11 @@
"numberReady" "numberReady"
], ],
"properties": { "properties": {
"collisionCount": {
"description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.",
"type": "integer",
"format": "int64"
},
"currentNumberScheduled": { "currentNumberScheduled": {
"description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/", "description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/",
"type": "integer", "type": "integer",
...@@ -7117,7 +7117,12 @@ ...@@ -7117,7 +7117,12 @@
"templateGeneration": { "templateGeneration": {
"type": "integer", "type": "integer",
"format": "int64", "format": "int64",
"description": "A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation." "description": "DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation."
},
"revisionHistoryLimit": {
"type": "integer",
"format": "int32",
"description": "The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10."
} }
} }
}, },
...@@ -9159,6 +9164,11 @@ ...@@ -9159,6 +9164,11 @@
"type": "integer", "type": "integer",
"format": "int32", "format": "int32",
"description": "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 spec.minReadySeconds)" "description": "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 spec.minReadySeconds)"
},
"collisionCount": {
"type": "integer",
"format": "int64",
"description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision."
} }
} }
}, },
......
...@@ -33,6 +33,7 @@ func startDaemonSetController(ctx ControllerContext) (bool, error) { ...@@ -33,6 +33,7 @@ func startDaemonSetController(ctx ControllerContext) (bool, error) {
} }
go daemon.NewDaemonSetsController( go daemon.NewDaemonSetsController(
ctx.InformerFactory.Extensions().V1beta1().DaemonSets(), ctx.InformerFactory.Extensions().V1beta1().DaemonSets(),
ctx.InformerFactory.Apps().V1beta1().ControllerRevisions(),
ctx.InformerFactory.Core().V1().Pods(), ctx.InformerFactory.Core().V1().Pods(),
ctx.InformerFactory.Core().V1().Nodes(), ctx.InformerFactory.Core().V1().Nodes(),
ctx.ClientBuilder.ClientOrDie("daemon-set-controller"), ctx.ClientBuilder.ClientOrDie("daemon-set-controller"),
......
...@@ -6607,7 +6607,7 @@ Examples:<br> ...@@ -6607,7 +6607,7 @@ Examples:<br>
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2017-05-31 19:35:31 UTC Last updated 2017-06-01 17:29:15 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -6634,7 +6634,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } ...@@ -6634,7 +6634,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2017-05-25 16:54:16 UTC Last updated 2017-05-25 21:02:39 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -689,6 +689,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } ...@@ -689,6 +689,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">collisionCount</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int64)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -2362,11 +2369,18 @@ When an object is created, the system will populate this list with the current s ...@@ -2362,11 +2369,18 @@ When an object is created, the system will populate this list with the current s
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">templateGeneration</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">templateGeneration</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int64)</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">integer (int64)</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">revisionHistoryLimit</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -8134,7 +8148,7 @@ Both these may change in the future. Incoming requests are matched against the h ...@@ -8134,7 +8148,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2017-05-31 19:36:09 UTC Last updated 2017-06-01 17:29:53 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -12964,6 +12964,11 @@ ...@@ -12964,6 +12964,11 @@
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
}, },
"revisionHistoryLimit": {
"description": "The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.",
"type": "integer",
"format": "int32"
},
"selector": { "selector": {
"description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "description": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector"
...@@ -12973,7 +12978,7 @@ ...@@ -12973,7 +12978,7 @@
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.PodTemplateSpec" "$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.PodTemplateSpec"
}, },
"templateGeneration": { "templateGeneration": {
"description": "A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.", "description": "DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.",
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
...@@ -12992,6 +12997,11 @@ ...@@ -12992,6 +12997,11 @@
"numberReady" "numberReady"
], ],
"properties": { "properties": {
"collisionCount": {
"description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.",
"type": "integer",
"format": "int64"
},
"currentNumberScheduled": { "currentNumberScheduled": {
"description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/", "description": "The number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/",
"type": "integer", "type": "integer",
......
...@@ -4861,7 +4861,12 @@ ...@@ -4861,7 +4861,12 @@
"templateGeneration": { "templateGeneration": {
"type": "integer", "type": "integer",
"format": "int64", "format": "int64",
"description": "A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation." "description": "DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation."
},
"revisionHistoryLimit": {
"type": "integer",
"format": "int32",
"description": "The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10."
} }
} }
}, },
...@@ -6903,6 +6908,11 @@ ...@@ -6903,6 +6908,11 @@
"type": "integer", "type": "integer",
"format": "int32", "format": "int32",
"description": "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 spec.minReadySeconds)" "description": "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 spec.minReadySeconds)"
},
"collisionCount": {
"type": "integer",
"format": "int64",
"description": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision."
} }
} }
}, },
......
...@@ -671,6 +671,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } ...@@ -671,6 +671,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">collisionCount</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int64)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -2193,11 +2200,18 @@ When an object is created, the system will populate this list with the current s ...@@ -2193,11 +2200,18 @@ When an object is created, the system will populate this list with the current s
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">templateGeneration</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">templateGeneration</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int64)</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">integer (int64)</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">revisionHistoryLimit</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -7204,7 +7218,7 @@ Both these may change in the future. Incoming requests are matched against the h ...@@ -7204,7 +7218,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2017-05-31 19:38:40 UTC Last updated 2017-06-01 17:32:20 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -553,6 +553,11 @@ func extensionFuncs(t apitesting.TestingCommon) []interface{} { ...@@ -553,6 +553,11 @@ func extensionFuncs(t apitesting.TestingCommon) []interface{} {
} }
} }
}, },
func(j *extensions.DaemonSetSpec, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again
rhl := int32(c.Rand.Int31())
j.RevisionHistoryLimit = &rhl
},
func(j *extensions.DaemonSetUpdateStrategy, c fuzz.Continue) { func(j *extensions.DaemonSetUpdateStrategy, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again c.FuzzNoCustom(j) // fuzz self without calling this function again
// Ensure that strategyType is one of valid values. // Ensure that strategyType is one of valid values.
......
...@@ -386,7 +386,7 @@ type DaemonSetUpdateStrategy struct { ...@@ -386,7 +386,7 @@ type DaemonSetUpdateStrategy struct {
// Rolling update config params. Present only if type = "RollingUpdate". // Rolling update config params. Present only if type = "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 Deployment `strategy.rollingUpdate`.
// See https://github.com/kubernetes/kubernetes/issues/35345 // See https://github.com/kubernetes/kubernetes/issues/35345
// +optional // +optional
RollingUpdate *RollingUpdateDaemonSet RollingUpdate *RollingUpdateDaemonSet
...@@ -449,10 +449,17 @@ type DaemonSetSpec struct { ...@@ -449,10 +449,17 @@ type DaemonSetSpec struct {
// +optional // +optional
MinReadySeconds int32 MinReadySeconds int32
// DEPRECATED.
// A sequence number representing a specific generation of the template. // A sequence number representing a specific generation of the template.
// Populated by the system. It can be set only during the creation. // Populated by the system. It can be set only during the creation.
// +optional // +optional
TemplateGeneration int64 TemplateGeneration int64
// The number of old history to retain to allow rollback.
// This is a pointer to distinguish between explicit zero and not specified.
// Defaults to 10.
// +optional
RevisionHistoryLimit *int32
} }
// DaemonSetStatus represents the current status of a daemon set. // DaemonSetStatus represents the current status of a daemon set.
...@@ -492,6 +499,12 @@ type DaemonSetStatus struct { ...@@ -492,6 +499,12 @@ type DaemonSetStatus struct {
// (ready for at least spec.minReadySeconds) // (ready for at least spec.minReadySeconds)
// +optional // +optional
NumberUnavailable int32 NumberUnavailable int32
// Count of hash collisions for the DaemonSet. The DaemonSet controller
// uses this field as a collision avoidance mechanism when it needs to
// create the name for the newest ControllerRevision.
// +optional
CollisionCount *int64
} }
// +genclient=true // +genclient=true
...@@ -519,10 +532,16 @@ type DaemonSet struct { ...@@ -519,10 +532,16 @@ type DaemonSet struct {
} }
const ( const (
// DEPRECATED: DefaultDaemonSetUniqueLabelKey is used instead.
// DaemonSetTemplateGenerationKey is the key of the labels that is added // DaemonSetTemplateGenerationKey is the key of the labels that is added
// to daemon set pods to distinguish between old and new pod templates // to daemon set pods to distinguish between old and new pod templates
// during DaemonSet template update. // during DaemonSet template update.
DaemonSetTemplateGenerationKey string = "pod-template-generation" DaemonSetTemplateGenerationKey string = "pod-template-generation"
// DefaultDaemonSetUniqueLabelKey is the default label key that is added
// to existing DaemonSet pods to distinguish between old and new
// DaemonSet pods during DaemonSet template updates.
DefaultDaemonSetUniqueLabelKey string = "daemonset-controller-hash"
) )
// DaemonSetList is a collection of daemon sets. // DaemonSetList is a collection of daemon sets.
......
...@@ -56,6 +56,10 @@ func SetDefaults_DaemonSet(obj *DaemonSet) { ...@@ -56,6 +56,10 @@ func SetDefaults_DaemonSet(obj *DaemonSet) {
updateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable updateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable
} }
} }
if obj.Spec.RevisionHistoryLimit == nil {
obj.Spec.RevisionHistoryLimit = new(int32)
*obj.Spec.RevisionHistoryLimit = 10
}
} }
func SetDefaults_Deployment(obj *Deployment) { func SetDefaults_Deployment(obj *Deployment) {
......
...@@ -32,7 +32,7 @@ import ( ...@@ -32,7 +32,7 @@ import (
. "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" . "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
) )
func TestSetDefaultDaemonSet(t *testing.T) { func TestSetDefaultDaemonSetSpec(t *testing.T) {
defaultLabels := map[string]string{"foo": "bar"} defaultLabels := map[string]string{"foo": "bar"}
period := int64(v1.DefaultTerminationGracePeriodSeconds) period := int64(v1.DefaultTerminationGracePeriodSeconds)
defaultTemplate := v1.PodTemplateSpec{ defaultTemplate := v1.PodTemplateSpec{
...@@ -78,6 +78,7 @@ func TestSetDefaultDaemonSet(t *testing.T) { ...@@ -78,6 +78,7 @@ func TestSetDefaultDaemonSet(t *testing.T) {
UpdateStrategy: DaemonSetUpdateStrategy{ UpdateStrategy: DaemonSetUpdateStrategy{
Type: OnDeleteDaemonSetStrategyType, Type: OnDeleteDaemonSetStrategyType,
}, },
RevisionHistoryLimit: newInt32(10),
}, },
}, },
}, },
...@@ -89,7 +90,8 @@ func TestSetDefaultDaemonSet(t *testing.T) { ...@@ -89,7 +90,8 @@ func TestSetDefaultDaemonSet(t *testing.T) {
}, },
}, },
Spec: DaemonSetSpec{ Spec: DaemonSetSpec{
Template: defaultTemplate, Template: defaultTemplate,
RevisionHistoryLimit: newInt32(1),
}, },
}, },
expected: &DaemonSet{ expected: &DaemonSet{
...@@ -106,6 +108,7 @@ func TestSetDefaultDaemonSet(t *testing.T) { ...@@ -106,6 +108,7 @@ func TestSetDefaultDaemonSet(t *testing.T) {
UpdateStrategy: DaemonSetUpdateStrategy{ UpdateStrategy: DaemonSetUpdateStrategy{
Type: OnDeleteDaemonSetStrategyType, Type: OnDeleteDaemonSetStrategyType,
}, },
RevisionHistoryLimit: newInt32(1),
}, },
}, },
}, },
...@@ -117,19 +120,7 @@ func TestSetDefaultDaemonSet(t *testing.T) { ...@@ -117,19 +120,7 @@ func TestSetDefaultDaemonSet(t *testing.T) {
UpdateStrategy: DaemonSetUpdateStrategy{ UpdateStrategy: DaemonSetUpdateStrategy{
Type: OnDeleteDaemonSetStrategyType, Type: OnDeleteDaemonSetStrategyType,
}, },
}, RevisionHistoryLimit: newInt32(10),
},
},
{ // Update strategy.
original: &DaemonSet{
Spec: DaemonSetSpec{},
},
expected: &DaemonSet{
Spec: DaemonSetSpec{
Template: templateNoLabel,
UpdateStrategy: DaemonSetUpdateStrategy{
Type: OnDeleteDaemonSetStrategyType,
},
}, },
}, },
}, },
...@@ -143,6 +134,7 @@ func TestSetDefaultDaemonSet(t *testing.T) { ...@@ -143,6 +134,7 @@ func TestSetDefaultDaemonSet(t *testing.T) {
UpdateStrategy: DaemonSetUpdateStrategy{ UpdateStrategy: DaemonSetUpdateStrategy{
Type: OnDeleteDaemonSetStrategyType, Type: OnDeleteDaemonSetStrategyType,
}, },
RevisionHistoryLimit: newInt32(10),
}, },
}, },
}, },
......
...@@ -123,10 +123,17 @@ message DaemonSetSpec { ...@@ -123,10 +123,17 @@ message DaemonSetSpec {
// +optional // +optional
optional int32 minReadySeconds = 4; optional int32 minReadySeconds = 4;
// DEPRECATED.
// A sequence number representing a specific generation of the template. // A sequence number representing a specific generation of the template.
// Populated by the system. It can be set only during the creation. // Populated by the system. It can be set only during the creation.
// +optional // +optional
optional int64 templateGeneration = 5; optional int64 templateGeneration = 5;
// The number of old history to retain to allow rollback.
// This is a pointer to distinguish between explicit zero and not specified.
// Defaults to 10.
// +optional
optional int32 revisionHistoryLimit = 6;
} }
// DaemonSetStatus represents the current status of a daemon set. // DaemonSetStatus represents the current status of a daemon set.
...@@ -169,6 +176,12 @@ message DaemonSetStatus { ...@@ -169,6 +176,12 @@ message DaemonSetStatus {
// (ready for at least spec.minReadySeconds) // (ready for at least spec.minReadySeconds)
// +optional // +optional
optional int32 numberUnavailable = 8; optional int32 numberUnavailable = 8;
// Count of hash collisions for the DaemonSet. The DaemonSet controller
// uses this field as a collision avoidance mechanism when it needs to
// create the name for the newest ControllerRevision.
// +optional
optional int64 collisionCount = 9;
} }
message DaemonSetUpdateStrategy { message DaemonSetUpdateStrategy {
...@@ -180,7 +193,7 @@ message DaemonSetUpdateStrategy { ...@@ -180,7 +193,7 @@ message DaemonSetUpdateStrategy {
// Rolling update config params. Present only if type = "RollingUpdate". // Rolling update config params. Present only if type = "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 Deployment `strategy.rollingUpdate`.
// See https://github.com/kubernetes/kubernetes/issues/35345 // See https://github.com/kubernetes/kubernetes/issues/35345
// +optional // +optional
optional RollingUpdateDaemonSet rollingUpdate = 2; optional RollingUpdateDaemonSet rollingUpdate = 2;
......
...@@ -386,7 +386,7 @@ type DaemonSetUpdateStrategy struct { ...@@ -386,7 +386,7 @@ type DaemonSetUpdateStrategy struct {
// Rolling update config params. Present only if type = "RollingUpdate". // Rolling update config params. Present only if type = "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 Deployment `strategy.rollingUpdate`.
// See https://github.com/kubernetes/kubernetes/issues/35345 // See https://github.com/kubernetes/kubernetes/issues/35345
// +optional // +optional
RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"` RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"`
...@@ -449,10 +449,17 @@ type DaemonSetSpec struct { ...@@ -449,10 +449,17 @@ type DaemonSetSpec struct {
// +optional // +optional
MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"`
// DEPRECATED.
// A sequence number representing a specific generation of the template. // A sequence number representing a specific generation of the template.
// Populated by the system. It can be set only during the creation. // Populated by the system. It can be set only during the creation.
// +optional // +optional
TemplateGeneration int64 `json:"templateGeneration,omitempty" protobuf:"varint,5,opt,name=templateGeneration"` TemplateGeneration int64 `json:"templateGeneration,omitempty" protobuf:"varint,5,opt,name=templateGeneration"`
// The number of old history to retain to allow rollback.
// This is a pointer to distinguish between explicit zero and not specified.
// Defaults to 10.
// +optional
RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"`
} }
// DaemonSetStatus represents the current status of a daemon set. // DaemonSetStatus represents the current status of a daemon set.
...@@ -495,6 +502,12 @@ type DaemonSetStatus struct { ...@@ -495,6 +502,12 @@ type DaemonSetStatus struct {
// (ready for at least spec.minReadySeconds) // (ready for at least spec.minReadySeconds)
// +optional // +optional
NumberUnavailable int32 `json:"numberUnavailable,omitempty" protobuf:"varint,8,opt,name=numberUnavailable"` NumberUnavailable int32 `json:"numberUnavailable,omitempty" protobuf:"varint,8,opt,name=numberUnavailable"`
// Count of hash collisions for the DaemonSet. The DaemonSet controller
// uses this field as a collision avoidance mechanism when it needs to
// create the name for the newest ControllerRevision.
// +optional
CollisionCount *int64 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"`
} }
// +genclient=true // +genclient=true
...@@ -522,10 +535,16 @@ type DaemonSet struct { ...@@ -522,10 +535,16 @@ type DaemonSet struct {
} }
const ( const (
// DEPRECATED: DefaultDaemonSetUniqueLabelKey is used instead.
// DaemonSetTemplateGenerationKey is the key of the labels that is added // DaemonSetTemplateGenerationKey is the key of the labels that is added
// to daemon set pods to distinguish between old and new pod templates // to daemon set pods to distinguish between old and new pod templates
// during DaemonSet template update. // during DaemonSet template update.
DaemonSetTemplateGenerationKey string = "pod-template-generation" DaemonSetTemplateGenerationKey string = "pod-template-generation"
// DefaultDaemonSetUniqueLabelKey is the default label key that is added
// to existing DaemonSet pods to distinguish between old and new
// DaemonSet pods during DaemonSet template updates.
DefaultDaemonSetUniqueLabelKey string = "daemonset-controller-hash"
) )
// DaemonSetList is a collection of daemon sets. // DaemonSetList is a collection of daemon sets.
......
...@@ -77,12 +77,13 @@ func (DaemonSetList) SwaggerDoc() map[string]string { ...@@ -77,12 +77,13 @@ func (DaemonSetList) SwaggerDoc() map[string]string {
} }
var map_DaemonSetSpec = map[string]string{ var map_DaemonSetSpec = map[string]string{
"": "DaemonSetSpec is the specification of a daemon set.", "": "DaemonSetSpec is the specification of a daemon set.",
"selector": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "selector": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors",
"template": "An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template's node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", "template": "An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template's node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template",
"updateStrategy": "An update strategy to replace existing DaemonSet pods with new pods.", "updateStrategy": "An update strategy to replace existing DaemonSet pods with new pods.",
"minReadySeconds": "The 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).", "minReadySeconds": "The 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).",
"templateGeneration": "A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.", "templateGeneration": "DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.",
"revisionHistoryLimit": "The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.",
} }
func (DaemonSetSpec) SwaggerDoc() map[string]string { func (DaemonSetSpec) SwaggerDoc() map[string]string {
...@@ -99,6 +100,7 @@ var map_DaemonSetStatus = map[string]string{ ...@@ -99,6 +100,7 @@ var map_DaemonSetStatus = map[string]string{
"updatedNumberScheduled": "The total number of nodes that are running updated daemon pod", "updatedNumberScheduled": "The total number of nodes that are running updated daemon pod",
"numberAvailable": "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 spec.minReadySeconds)", "numberAvailable": "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 spec.minReadySeconds)",
"numberUnavailable": "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 spec.minReadySeconds)", "numberUnavailable": "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 spec.minReadySeconds)",
"collisionCount": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.",
} }
func (DaemonSetStatus) SwaggerDoc() map[string]string { func (DaemonSetStatus) SwaggerDoc() map[string]string {
......
...@@ -352,6 +352,7 @@ func autoConvert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *DaemonSet ...@@ -352,6 +352,7 @@ func autoConvert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *DaemonSet
} }
out.MinReadySeconds = in.MinReadySeconds out.MinReadySeconds = in.MinReadySeconds
out.TemplateGeneration = in.TemplateGeneration out.TemplateGeneration = in.TemplateGeneration
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
return nil return nil
} }
...@@ -370,6 +371,7 @@ func autoConvert_extensions_DaemonSetSpec_To_v1beta1_DaemonSetSpec(in *extension ...@@ -370,6 +371,7 @@ func autoConvert_extensions_DaemonSetSpec_To_v1beta1_DaemonSetSpec(in *extension
} }
out.MinReadySeconds = in.MinReadySeconds out.MinReadySeconds = in.MinReadySeconds
out.TemplateGeneration = in.TemplateGeneration out.TemplateGeneration = in.TemplateGeneration
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
return nil return nil
} }
...@@ -387,6 +389,7 @@ func autoConvert_v1beta1_DaemonSetStatus_To_extensions_DaemonSetStatus(in *Daemo ...@@ -387,6 +389,7 @@ func autoConvert_v1beta1_DaemonSetStatus_To_extensions_DaemonSetStatus(in *Daemo
out.UpdatedNumberScheduled = in.UpdatedNumberScheduled out.UpdatedNumberScheduled = in.UpdatedNumberScheduled
out.NumberAvailable = in.NumberAvailable out.NumberAvailable = in.NumberAvailable
out.NumberUnavailable = in.NumberUnavailable out.NumberUnavailable = in.NumberUnavailable
out.CollisionCount = (*int64)(unsafe.Pointer(in.CollisionCount))
return nil return nil
} }
...@@ -404,6 +407,7 @@ func autoConvert_extensions_DaemonSetStatus_To_v1beta1_DaemonSetStatus(in *exten ...@@ -404,6 +407,7 @@ func autoConvert_extensions_DaemonSetStatus_To_v1beta1_DaemonSetStatus(in *exten
out.UpdatedNumberScheduled = in.UpdatedNumberScheduled out.UpdatedNumberScheduled = in.UpdatedNumberScheduled
out.NumberAvailable = in.NumberAvailable out.NumberAvailable = in.NumberAvailable
out.NumberUnavailable = in.NumberUnavailable out.NumberUnavailable = in.NumberUnavailable
out.CollisionCount = (*int64)(unsafe.Pointer(in.CollisionCount))
return nil return nil
} }
......
...@@ -182,6 +182,9 @@ func DeepCopy_v1beta1_DaemonSet(in interface{}, out interface{}, c *conversion.C ...@@ -182,6 +182,9 @@ func DeepCopy_v1beta1_DaemonSet(in interface{}, out interface{}, c *conversion.C
if err := DeepCopy_v1beta1_DaemonSetSpec(&in.Spec, &out.Spec, c); err != nil { if err := DeepCopy_v1beta1_DaemonSetSpec(&in.Spec, &out.Spec, c); err != nil {
return err return err
} }
if err := DeepCopy_v1beta1_DaemonSetStatus(&in.Status, &out.Status, c); err != nil {
return err
}
return nil return nil
} }
} }
...@@ -225,6 +228,11 @@ func DeepCopy_v1beta1_DaemonSetSpec(in interface{}, out interface{}, c *conversi ...@@ -225,6 +228,11 @@ func DeepCopy_v1beta1_DaemonSetSpec(in interface{}, out interface{}, c *conversi
if err := DeepCopy_v1beta1_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, c); err != nil { if err := DeepCopy_v1beta1_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, c); err != nil {
return err return err
} }
if in.RevisionHistoryLimit != nil {
in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit
*out = new(int32)
**out = **in
}
return nil return nil
} }
} }
...@@ -235,6 +243,11 @@ func DeepCopy_v1beta1_DaemonSetStatus(in interface{}, out interface{}, c *conver ...@@ -235,6 +243,11 @@ func DeepCopy_v1beta1_DaemonSetStatus(in interface{}, out interface{}, c *conver
in := in.(*DaemonSetStatus) in := in.(*DaemonSetStatus)
out := out.(*DaemonSetStatus) out := out.(*DaemonSetStatus)
*out = *in *out = *in
if in.CollisionCount != nil {
in, out := &in.CollisionCount, &out.CollisionCount
*out = new(int64)
**out = **in
}
return nil return nil
} }
} }
......
...@@ -134,6 +134,9 @@ func validateDaemonSetStatus(status *extensions.DaemonSetStatus, fldPath *field. ...@@ -134,6 +134,9 @@ func validateDaemonSetStatus(status *extensions.DaemonSetStatus, fldPath *field.
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedNumberScheduled), fldPath.Child("updatedNumberScheduled"))...) 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.NumberAvailable), fldPath.Child("numberAvailable"))...)
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberUnavailable), fldPath.Child("numberUnavailable"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberUnavailable), fldPath.Child("numberUnavailable"))...)
if status.CollisionCount != nil {
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...)
}
return allErrs return allErrs
} }
...@@ -141,6 +144,13 @@ func validateDaemonSetStatus(status *extensions.DaemonSetStatus, fldPath *field. ...@@ -141,6 +144,13 @@ func validateDaemonSetStatus(status *extensions.DaemonSetStatus, fldPath *field.
func ValidateDaemonSetStatusUpdate(ds, oldDS *extensions.DaemonSet) field.ErrorList { func ValidateDaemonSetStatusUpdate(ds, oldDS *extensions.DaemonSet) field.ErrorList {
allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata")) allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, validateDaemonSetStatus(&ds.Status, field.NewPath("status"))...) allErrs = append(allErrs, validateDaemonSetStatus(&ds.Status, field.NewPath("status"))...)
if isDecremented(ds.Status.CollisionCount, oldDS.Status.CollisionCount) {
value := int64(0)
if ds.Status.CollisionCount != nil {
value = *ds.Status.CollisionCount
}
allErrs = append(allErrs, field.Invalid(field.NewPath("status").Child("collisionCount"), value, "cannot be decremented"))
}
return allErrs return allErrs
} }
...@@ -172,6 +182,10 @@ func ValidateDaemonSetSpec(spec *extensions.DaemonSetSpec, fldPath *field.Path) ...@@ -172,6 +182,10 @@ func ValidateDaemonSetSpec(spec *extensions.DaemonSetSpec, fldPath *field.Path)
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.TemplateGeneration), fldPath.Child("templateGeneration"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.TemplateGeneration), fldPath.Child("templateGeneration"))...)
allErrs = append(allErrs, ValidateDaemonSetUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...) allErrs = append(allErrs, ValidateDaemonSetUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...)
if spec.RevisionHistoryLimit != nil {
// zero is a valid RevisionHistoryLimit
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...)
}
return allErrs return allErrs
} }
......
...@@ -183,6 +183,9 @@ func DeepCopy_extensions_DaemonSet(in interface{}, out interface{}, c *conversio ...@@ -183,6 +183,9 @@ func DeepCopy_extensions_DaemonSet(in interface{}, out interface{}, c *conversio
if err := DeepCopy_extensions_DaemonSetSpec(&in.Spec, &out.Spec, c); err != nil { if err := DeepCopy_extensions_DaemonSetSpec(&in.Spec, &out.Spec, c); err != nil {
return err return err
} }
if err := DeepCopy_extensions_DaemonSetStatus(&in.Status, &out.Status, c); err != nil {
return err
}
return nil return nil
} }
} }
...@@ -226,6 +229,11 @@ func DeepCopy_extensions_DaemonSetSpec(in interface{}, out interface{}, c *conve ...@@ -226,6 +229,11 @@ func DeepCopy_extensions_DaemonSetSpec(in interface{}, out interface{}, c *conve
if err := DeepCopy_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, c); err != nil { if err := DeepCopy_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, c); err != nil {
return err return err
} }
if in.RevisionHistoryLimit != nil {
in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit
*out = new(int32)
**out = **in
}
return nil return nil
} }
} }
...@@ -236,6 +244,11 @@ func DeepCopy_extensions_DaemonSetStatus(in interface{}, out interface{}, c *con ...@@ -236,6 +244,11 @@ func DeepCopy_extensions_DaemonSetStatus(in interface{}, out interface{}, c *con
in := in.(*DaemonSetStatus) in := in.(*DaemonSetStatus)
out := out.(*DaemonSetStatus) out := out.(*DaemonSetStatus)
*out = *in *out = *in
if in.CollisionCount != nil {
in, out := &in.CollisionCount, &out.CollisionCount
*out = new(int64)
**out = **in
}
return nil return nil
} }
} }
......
...@@ -26,6 +26,7 @@ go_library( ...@@ -26,6 +26,7 @@ go_library(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/api/v1:go_default_library", "//pkg/api/v1:go_default_library",
"//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
) )
...@@ -29,6 +30,7 @@ import ( ...@@ -29,6 +30,7 @@ import (
// DaemonSetLister. // DaemonSetLister.
type DaemonSetListerExpansion interface { type DaemonSetListerExpansion interface {
GetPodDaemonSets(pod *v1.Pod) ([]*v1beta1.DaemonSet, error) GetPodDaemonSets(pod *v1.Pod) ([]*v1beta1.DaemonSet, error)
GetHistoryDaemonSets(history *apps.ControllerRevision) ([]*v1beta1.DaemonSet, error)
} }
// DaemonSetNamespaceListerExpansion allows custom methods to be added to // DaemonSetNamespaceListerExpansion allows custom methods to be added to
...@@ -76,3 +78,37 @@ func (s *daemonSetLister) GetPodDaemonSets(pod *v1.Pod) ([]*v1beta1.DaemonSet, e ...@@ -76,3 +78,37 @@ func (s *daemonSetLister) GetPodDaemonSets(pod *v1.Pod) ([]*v1beta1.DaemonSet, e
return daemonSets, nil return daemonSets, nil
} }
// GetHistoryDaemonSets returns a list of DaemonSets that potentially
// match a ControllerRevision. Only the one specified in the ControllerRevision's ControllerRef
// will actually manage it.
// Returns an error only if no matching DaemonSets are found.
func (s *daemonSetLister) GetHistoryDaemonSets(history *apps.ControllerRevision) ([]*v1beta1.DaemonSet, error) {
if len(history.Labels) == 0 {
return nil, fmt.Errorf("no DaemonSet found for ControllerRevision %s because it has no labels", history.Name)
}
list, err := s.DaemonSets(history.Namespace).List(labels.Everything())
if err != nil {
return nil, err
}
var daemonSets []*v1beta1.DaemonSet
for _, ds := range list {
selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
// If a DaemonSet with a nil or empty selector creeps in, it should match nothing, not everything.
if selector.Empty() || !selector.Matches(labels.Set(history.Labels)) {
continue
}
daemonSets = append(daemonSets, ds)
}
if len(daemonSets) == 0 {
return nil, fmt.Errorf("could not find DaemonSets for ControllerRevision %s in namespace %s with labels: %v", history.Name, history.Namespace, history.Labels)
}
return daemonSets, nil
}
...@@ -17,8 +17,10 @@ limitations under the License. ...@@ -17,8 +17,10 @@ limitations under the License.
package controller package controller
import ( import (
"encoding/binary"
"encoding/json" "encoding/json"
"fmt" "fmt"
"hash/fnv"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
...@@ -29,16 +31,14 @@ import ( ...@@ -29,16 +31,14 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/clock"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/integer" "k8s.io/client-go/util/integer"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
v1helper "k8s.io/kubernetes/pkg/api/v1/helper" v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
...@@ -48,6 +48,7 @@ import ( ...@@ -48,6 +48,7 @@ import (
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
clientretry "k8s.io/kubernetes/pkg/client/retry" clientretry "k8s.io/kubernetes/pkg/client/retry"
hashutil "k8s.io/kubernetes/pkg/util/hash"
"github.com/golang/glog" "github.com/golang/glog"
) )
...@@ -980,3 +981,18 @@ func WaitForCacheSync(controllerName string, stopCh <-chan struct{}, cacheSyncs ...@@ -980,3 +981,18 @@ func WaitForCacheSync(controllerName string, stopCh <-chan struct{}, cacheSyncs
glog.Infof("Caches are synced for %s controller", controllerName) glog.Infof("Caches are synced for %s controller", controllerName)
return true return true
} }
// ComputeHash returns a hash value calculated from pod template and a collisionCount to avoid hash collision
func ComputeHash(template *v1.PodTemplateSpec, collisionCount *int64) uint32 {
podTemplateSpecHasher := fnv.New32a()
hashutil.DeepHashObject(podTemplateSpecHasher, *template)
// Add collisionCount in the hash if it exists.
if collisionCount != nil {
collisionCountBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(collisionCountBytes, uint64(*collisionCount))
podTemplateSpecHasher.Write(collisionCountBytes)
}
return podTemplateSpecHasher.Sum32()
}
...@@ -19,6 +19,7 @@ package controller ...@@ -19,6 +19,7 @@ package controller
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"math/rand" "math/rand"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
...@@ -443,3 +444,38 @@ func TestActiveReplicaSetsFiltering(t *testing.T) { ...@@ -443,3 +444,38 @@ func TestActiveReplicaSetsFiltering(t *testing.T) {
t.Errorf("expected %v, got %v", expectedNames.List(), gotNames.List()) t.Errorf("expected %v, got %v", expectedNames.List(), gotNames.List())
} }
} }
func int64P(num int64) *int64 {
return &num
}
func TestComputeHash(t *testing.T) {
tests := []struct {
name string
template *v1.PodTemplateSpec
collisionCount *int64
otherCollisionCount *int64
}{
{
name: "simple",
template: &v1.PodTemplateSpec{},
collisionCount: int64P(1),
otherCollisionCount: int64P(2),
},
{
name: "using math.MaxInt64",
template: &v1.PodTemplateSpec{},
collisionCount: nil,
otherCollisionCount: int64P(int64(math.MaxInt64)),
},
}
for _, test := range tests {
hash := ComputeHash(test.template, test.collisionCount)
otherHash := ComputeHash(test.template, test.otherCollisionCount)
if hash == otherHash {
t.Errorf("expected different hashes but got the same: %d", hash)
}
}
}
...@@ -21,25 +21,31 @@ go_library( ...@@ -21,25 +21,31 @@ go_library(
"//pkg/api/v1:go_default_library", "//pkg/api/v1:go_default_library",
"//pkg/api/v1/helper:go_default_library", "//pkg/api/v1/helper:go_default_library",
"//pkg/api/v1/pod:go_default_library", "//pkg/api/v1/pod:go_default_library",
"//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library", "//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/clientset_generated/clientset/typed/extensions/v1beta1:go_default_library", "//pkg/client/clientset_generated/clientset/typed/extensions/v1beta1:go_default_library",
"//pkg/client/informers/informers_generated/externalversions/apps/v1beta1:go_default_library",
"//pkg/client/informers/informers_generated/externalversions/core/v1:go_default_library", "//pkg/client/informers/informers_generated/externalversions/core/v1:go_default_library",
"//pkg/client/informers/informers_generated/externalversions/extensions/v1beta1:go_default_library", "//pkg/client/informers/informers_generated/externalversions/extensions/v1beta1:go_default_library",
"//pkg/client/listers/apps/v1beta1:go_default_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/daemon/util:go_default_library",
"//pkg/features:go_default_library", "//pkg/features:go_default_library",
"//pkg/kubelet/types:go_default_library", "//pkg/kubelet/types:go_default_library",
"//pkg/util/labels: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",
"//plugin/pkg/scheduler/schedulercache:go_default_library", "//plugin/pkg/scheduler/schedulercache:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
...@@ -72,6 +78,7 @@ go_test( ...@@ -72,6 +78,7 @@ go_test(
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/kubelet/types:go_default_library", "//pkg/kubelet/types:go_default_library",
"//pkg/securitycontext:go_default_library", "//pkg/securitycontext:go_default_library",
"//pkg/util/labels:go_default_library",
"//plugin/pkg/scheduler/algorithm:go_default_library", "//plugin/pkg/scheduler/algorithm:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
......
...@@ -29,9 +29,10 @@ import ( ...@@ -29,9 +29,10 @@ import (
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
) )
// GetPodTemplateWithHash returns copy of provided template with additional // CreatePodTemplate returns copy of provided template with additional
// label which contains hash of provided template and sets default daemon tolerations. // label which contains templateGeneration (for backward compatibility),
func GetPodTemplateWithGeneration(template v1.PodTemplateSpec, generation int64) v1.PodTemplateSpec { // hash of provided template and sets default daemon tolerations.
func CreatePodTemplate(template v1.PodTemplateSpec, generation int64, hash string) v1.PodTemplateSpec {
obj, _ := api.Scheme.DeepCopy(template) obj, _ := api.Scheme.DeepCopy(template)
newTemplate := obj.(v1.PodTemplateSpec) newTemplate := obj.(v1.PodTemplateSpec)
// DaemonSet pods shouldn't be deleted by NodeController in case of node problems. // DaemonSet pods shouldn't be deleted by NodeController in case of node problems.
...@@ -60,14 +61,19 @@ func GetPodTemplateWithGeneration(template v1.PodTemplateSpec, generation int64) ...@@ -60,14 +61,19 @@ func GetPodTemplateWithGeneration(template v1.PodTemplateSpec, generation int64)
extensions.DaemonSetTemplateGenerationKey, extensions.DaemonSetTemplateGenerationKey,
templateGenerationStr, templateGenerationStr,
) )
// TODO: do we need to validate if the DaemonSet is RollingUpdate or not?
if len(hash) > 0 {
newTemplate.ObjectMeta.Labels[extensions.DefaultDaemonSetUniqueLabelKey] = hash
}
return newTemplate return newTemplate
} }
// IsPodUpdate checks if pod contains label with provided hash // IsPodUpdate checks if pod contains label value that either matches templateGeneration or hash
func IsPodUpdated(dsTemplateGeneration int64, pod *v1.Pod) bool { func IsPodUpdated(dsTemplateGeneration int64, pod *v1.Pod, hash string) bool {
podTemplateGeneration, generationExists := pod.ObjectMeta.Labels[extensions.DaemonSetTemplateGenerationKey] // Compare with hash to see if the pod is updated, need to maintain backward compatibility of templateGeneration
dsTemplateGenerationStr := fmt.Sprint(dsTemplateGeneration) templateMatches := pod.Labels[extensions.DaemonSetTemplateGenerationKey] == fmt.Sprint(dsTemplateGeneration)
return generationExists && podTemplateGeneration == dsTemplateGenerationStr hashMatches := len(hash) > 0 && pod.Labels[extensions.DefaultDaemonSetUniqueLabelKey] == hash
return hashMatches || templateMatches
} }
// SplitByAvailablePods splits provided daemon set pods by availabilty // SplitByAvailablePods splits provided daemon set pods by availabilty
......
...@@ -47,46 +47,118 @@ func newPod(podName string, nodeName string, label map[string]string) *v1.Pod { ...@@ -47,46 +47,118 @@ func newPod(podName string, nodeName string, label map[string]string) *v1.Pod {
} }
func TestIsPodUpdated(t *testing.T) { func TestIsPodUpdated(t *testing.T) {
templateGeneration := int64(12345)
hash := "55555"
labels := map[string]string{extensions.DaemonSetTemplateGenerationKey: fmt.Sprint(templateGeneration), extensions.DefaultDaemonSetUniqueLabelKey: hash}
labelsNoHash := map[string]string{extensions.DaemonSetTemplateGenerationKey: fmt.Sprint(templateGeneration)}
tests := []struct { tests := []struct {
test string
templateGeneration int64 templateGeneration int64
pod *v1.Pod pod *v1.Pod
hash string
isUpdated bool isUpdated bool
}{ }{
{ {
int64(12345), "templateGeneration and hash both match",
newPod("pod1", "node1", map[string]string{extensions.DaemonSetTemplateGenerationKey: "12345"}), templateGeneration,
newPod("pod1", "node1", labels),
hash,
true, true,
}, },
{ {
int64(12355), "templateGeneration matches, hash doesn't",
newPod("pod1", "node1", map[string]string{extensions.DaemonSetTemplateGenerationKey: "12345"}), templateGeneration,
newPod("pod1", "node1", labels),
hash + "123",
true,
},
{
"templateGeneration matches, no hash label, has hash",
templateGeneration,
newPod("pod1", "node1", labelsNoHash),
hash,
true,
},
{
"templateGeneration matches, no hash label, no hash",
templateGeneration,
newPod("pod1", "node1", labelsNoHash),
"",
true,
},
{
"templateGeneration matches, has hash label, no hash",
templateGeneration,
newPod("pod1", "node1", labels),
"",
true,
},
{
"templateGeneration doesn't match, hash does",
templateGeneration + 1,
newPod("pod1", "node1", labels),
hash,
true,
},
{
"templateGeneration and hash don't match",
templateGeneration + 1,
newPod("pod1", "node1", labels),
hash + "123",
false, false,
}, },
{ {
int64(12355), "empty labels, no hash",
templateGeneration,
newPod("pod1", "node1", map[string]string{}), newPod("pod1", "node1", map[string]string{}),
"",
false, false,
}, },
{ {
int64(12355), "empty labels",
templateGeneration,
newPod("pod1", "node1", map[string]string{}),
hash,
false,
},
{
"no labels",
templateGeneration,
newPod("pod1", "node1", nil), newPod("pod1", "node1", nil),
hash,
false, false,
}, },
} }
for _, test := range tests { for _, test := range tests {
updated := IsPodUpdated(test.templateGeneration, test.pod) updated := IsPodUpdated(test.templateGeneration, test.pod, test.hash)
if updated != test.isUpdated { if updated != test.isUpdated {
t.Errorf("IsPodUpdated returned wrong value. Expected %t, got %t. TemplateGeneration: %d", test.isUpdated, updated, test.templateGeneration) t.Errorf("%s: IsPodUpdated returned wrong value. Expected %t, got %t", test.test, test.isUpdated, updated)
} }
} }
} }
func TestGetPodTemplateWithGeneration(t *testing.T) { func TestCreatePodTemplate(t *testing.T) {
generation := int64(1) tests := []struct {
podTemplateSpec := v1.PodTemplateSpec{} templateGeneration int64
newPodTemplate := GetPodTemplateWithGeneration(podTemplateSpec, generation) hash string
label, exists := newPodTemplate.ObjectMeta.Labels[extensions.DaemonSetTemplateGenerationKey] expectUniqueLabel bool
if !exists || label != fmt.Sprint(generation) { }{
t.Errorf("Error in getting podTemplateSpec with label generation. Exists: %t, label: %s", exists, label) {int64(1), "", false},
{int64(2), "3242341807", true},
}
for _, test := range tests {
podTemplateSpec := v1.PodTemplateSpec{}
newPodTemplate := CreatePodTemplate(podTemplateSpec, test.templateGeneration, test.hash)
val, exists := newPodTemplate.ObjectMeta.Labels[extensions.DaemonSetTemplateGenerationKey]
if !exists || val != fmt.Sprint(test.templateGeneration) {
t.Errorf("Expected podTemplateSpec to have generation label value: %d, got: %s", test.templateGeneration, val)
}
val, exists = newPodTemplate.ObjectMeta.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
if test.expectUniqueLabel && (!exists || val != test.hash) {
t.Errorf("Expected podTemplateSpec to have hash label value: %s, got: %s", test.hash, val)
}
if !test.expectUniqueLabel && exists {
t.Errorf("Expected podTemplateSpec to have no hash label, got: %s", val)
}
} }
} }
...@@ -288,7 +288,7 @@ func (dc *DeploymentController) getNewReplicaSet(d *extensions.Deployment, rsLis ...@@ -288,7 +288,7 @@ func (dc *DeploymentController) getNewReplicaSet(d *extensions.Deployment, rsLis
return nil, err return nil, err
} }
newRSTemplate := templateCopy.(v1.PodTemplateSpec) newRSTemplate := templateCopy.(v1.PodTemplateSpec)
podTemplateSpecHash := fmt.Sprintf("%d", deploymentutil.GetPodTemplateSpecHash(&newRSTemplate, d.Status.CollisionCount)) podTemplateSpecHash := fmt.Sprintf("%d", controller.ComputeHash(&newRSTemplate, d.Status.CollisionCount))
newRSTemplate.Labels = labelsutil.CloneAndAddLabel(d.Spec.Template.Labels, extensions.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash) newRSTemplate.Labels = labelsutil.CloneAndAddLabel(d.Spec.Template.Labels, extensions.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash)
// Add podTemplateHash label to selector. // Add podTemplateHash label to selector.
newRSSelector := labelsutil.CloneSelectorAndAddLabel(d.Spec.Selector, extensions.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash) newRSSelector := labelsutil.CloneSelectorAndAddLabel(d.Spec.Selector, extensions.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash)
......
...@@ -28,7 +28,6 @@ go_library( ...@@ -28,7 +28,6 @@ go_library(
"//pkg/client/listers/extensions/v1beta1:go_default_library", "//pkg/client/listers/extensions/v1beta1:go_default_library",
"//pkg/client/retry:go_default_library", "//pkg/client/retry:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/util/hash:go_default_library",
"//pkg/util/labels:go_default_library", "//pkg/util/labels:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
...@@ -48,7 +47,6 @@ go_test( ...@@ -48,7 +47,6 @@ go_test(
srcs = [ srcs = [
"deployment_util_test.go", "deployment_util_test.go",
"hash_test.go", "hash_test.go",
"pod_util_test.go",
], ],
library = ":go_default_library", library = ":go_default_library",
tags = ["automanaged"], tags = ["automanaged"],
......
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/controller"
hashutil "k8s.io/kubernetes/pkg/util/hash" hashutil "k8s.io/kubernetes/pkg/util/hash"
) )
...@@ -110,7 +111,7 @@ func TestPodTemplateSpecHash(t *testing.T) { ...@@ -110,7 +111,7 @@ func TestPodTemplateSpecHash(t *testing.T) {
specJson := strings.Replace(podSpec, "@@VERSION@@", strconv.Itoa(i), 1) specJson := strings.Replace(podSpec, "@@VERSION@@", strconv.Itoa(i), 1)
spec := v1.PodTemplateSpec{} spec := v1.PodTemplateSpec{}
json.Unmarshal([]byte(specJson), &spec) json.Unmarshal([]byte(specJson), &spec)
hash := GetPodTemplateSpecHash(&spec, nil) hash := controller.ComputeHash(&spec, nil)
if v, ok := seenHashes[hash]; ok { if v, ok := seenHashes[hash]; ok {
t.Errorf("Hash collision, old: %d new: %d", v, i) t.Errorf("Hash collision, old: %d new: %d", v, i)
break break
...@@ -139,6 +140,6 @@ func BenchmarkFnv(b *testing.B) { ...@@ -139,6 +140,6 @@ func BenchmarkFnv(b *testing.B) {
json.Unmarshal([]byte(podSpec), &spec) json.Unmarshal([]byte(podSpec), &spec)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
GetPodTemplateSpecHash(&spec, nil) controller.ComputeHash(&spec, nil)
} }
} }
...@@ -17,9 +17,6 @@ limitations under the License. ...@@ -17,9 +17,6 @@ limitations under the License.
package util package util
import ( import (
"encoding/binary"
"hash/fnv"
"github.com/golang/glog" "github.com/golang/glog"
errorsutil "k8s.io/apimachinery/pkg/util/errors" errorsutil "k8s.io/apimachinery/pkg/util/errors"
...@@ -28,23 +25,8 @@ import ( ...@@ -28,23 +25,8 @@ import (
v1core "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1" v1core "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
corelisters "k8s.io/kubernetes/pkg/client/listers/core/v1" corelisters "k8s.io/kubernetes/pkg/client/listers/core/v1"
"k8s.io/kubernetes/pkg/client/retry" "k8s.io/kubernetes/pkg/client/retry"
hashutil "k8s.io/kubernetes/pkg/util/hash"
) )
func GetPodTemplateSpecHash(template *v1.PodTemplateSpec, uniquifier *int64) uint32 {
podTemplateSpecHasher := fnv.New32a()
hashutil.DeepHashObject(podTemplateSpecHasher, *template)
// Add uniquifier in the hash if it exists.
if uniquifier != nil {
uniquifierBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(uniquifierBytes, uint64(*uniquifier))
podTemplateSpecHasher.Write(uniquifierBytes)
}
return podTemplateSpecHasher.Sum32()
}
// TODO: use client library instead when it starts to support update retries // TODO: use client library instead when it starts to support update retries
// see https://github.com/kubernetes/kubernetes/issues/21479 // see https://github.com/kubernetes/kubernetes/issues/21479
type updatePodFunc func(pod *v1.Pod) error type updatePodFunc func(pod *v1.Pod) error
......
/*
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 (
"math"
"testing"
"k8s.io/kubernetes/pkg/api/v1"
)
func int64P(num int64) *int64 {
return &num
}
func TestGetPodTemplateSpecHash(t *testing.T) {
tests := []struct {
name string
template *v1.PodTemplateSpec
collisionCount *int64
otherCollisionCount *int64
}{
{
name: "simple",
template: &v1.PodTemplateSpec{},
collisionCount: int64P(1),
otherCollisionCount: int64P(2),
},
{
name: "using math.MaxInt64",
template: &v1.PodTemplateSpec{},
collisionCount: nil,
otherCollisionCount: int64P(int64(math.MaxInt64)),
},
}
for _, test := range tests {
hash := GetPodTemplateSpecHash(test.template, test.collisionCount)
otherHash := GetPodTemplateSpecHash(test.template, test.otherCollisionCount)
if hash == otherHash {
t.Errorf("expected different hashes but got the same: %d", hash)
}
}
}
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
unversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/extensions/v1beta1" unversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/extensions/v1beta1"
extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/v1beta1" extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/retry" "k8s.io/kubernetes/pkg/client/retry"
"k8s.io/kubernetes/pkg/controller"
labelsutil "k8s.io/kubernetes/pkg/util/labels" labelsutil "k8s.io/kubernetes/pkg/util/labels"
) )
...@@ -76,5 +77,5 @@ func GetReplicaSetHash(rs *extensions.ReplicaSet, uniquifier *int64) (string, er ...@@ -76,5 +77,5 @@ func GetReplicaSetHash(rs *extensions.ReplicaSet, uniquifier *int64) (string, er
} }
rsTemplate := template.(v1.PodTemplateSpec) rsTemplate := template.(v1.PodTemplateSpec)
rsTemplate.Labels = labelsutil.CloneAndRemoveLabel(rsTemplate.Labels, extensions.DefaultDeploymentUniqueLabelKey) rsTemplate.Labels = labelsutil.CloneAndRemoveLabel(rsTemplate.Labels, extensions.DefaultDeploymentUniqueLabelKey)
return fmt.Sprintf("%d", GetPodTemplateSpecHash(&rsTemplate, uniquifier)), nil return fmt.Sprintf("%d", controller.ComputeHash(&rsTemplate, uniquifier)), nil
} }
...@@ -86,6 +86,7 @@ func init() { ...@@ -86,6 +86,7 @@ func init() {
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(), rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete", "patch").Groups(legacyGroup).Resources("pods").RuleOrDie(), rbac.NewRule("list", "watch", "create", "delete", "patch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("create").Groups(legacyGroup).Resources("pods/binding").RuleOrDie(), rbac.NewRule("create").Groups(legacyGroup).Resources("pods/binding").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete", "update", "patch").Groups(appsGroup).Resources("controllerrevisions").RuleOrDie(),
eventsRule(), eventsRule(),
}, },
}) })
......
...@@ -181,6 +181,17 @@ items: ...@@ -181,6 +181,17 @@ items:
verbs: verbs:
- create - create
- apiGroups: - apiGroups:
- apps
resources:
- controllerrevisions
verbs:
- create
- delete
- list
- patch
- update
- watch
- apiGroups:
- "" - ""
resources: resources:
- events - events
......
...@@ -28,6 +28,7 @@ go_library( ...@@ -28,6 +28,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/client-go/pkg/api/v1:go_default_library", "//vendor/k8s.io/client-go/pkg/api/v1:go_default_library",
"//vendor/k8s.io/client-go/pkg/apis/apps/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/pkg/apis/extensions/v1beta1:go_default_library", "//vendor/k8s.io/client-go/pkg/apis/extensions/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library",
], ],
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/api/v1"
apps "k8s.io/client-go/pkg/apis/apps/v1beta1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1" "k8s.io/client-go/pkg/apis/extensions/v1beta1"
) )
...@@ -29,6 +30,7 @@ import ( ...@@ -29,6 +30,7 @@ import (
// DaemonSetLister. // DaemonSetLister.
type DaemonSetListerExpansion interface { type DaemonSetListerExpansion interface {
GetPodDaemonSets(pod *v1.Pod) ([]*v1beta1.DaemonSet, error) GetPodDaemonSets(pod *v1.Pod) ([]*v1beta1.DaemonSet, error)
GetHistoryDaemonSets(history *apps.ControllerRevision) ([]*v1beta1.DaemonSet, error)
} }
// DaemonSetNamespaceListerExpansion allows custom methods to be added to // DaemonSetNamespaceListerExpansion allows custom methods to be added to
...@@ -76,3 +78,37 @@ func (s *daemonSetLister) GetPodDaemonSets(pod *v1.Pod) ([]*v1beta1.DaemonSet, e ...@@ -76,3 +78,37 @@ func (s *daemonSetLister) GetPodDaemonSets(pod *v1.Pod) ([]*v1beta1.DaemonSet, e
return daemonSets, nil return daemonSets, nil
} }
// GetHistoryDaemonSets returns a list of DaemonSets that potentially
// match a ControllerRevision. Only the one specified in the ControllerRevision's ControllerRef
// will actually manage it.
// Returns an error only if no matching DaemonSets are found.
func (s *daemonSetLister) GetHistoryDaemonSets(history *apps.ControllerRevision) ([]*v1beta1.DaemonSet, error) {
if len(history.Labels) == 0 {
return nil, fmt.Errorf("no DaemonSet found for ControllerRevision %s because it has no labels", history.Name)
}
list, err := s.DaemonSets(history.Namespace).List(labels.Everything())
if err != nil {
return nil, err
}
var daemonSets []*v1beta1.DaemonSet
for _, ds := range list {
selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
// If a DaemonSet with a nil or empty selector creeps in, it should match nothing, not everything.
if selector.Empty() || !selector.Matches(labels.Set(history.Labels)) {
continue
}
daemonSets = append(daemonSets, ds)
}
if len(daemonSets) == 0 {
return nil, fmt.Errorf("could not find DaemonSets for ControllerRevision %s in namespace %s with labels: %v", history.Name, history.Namespace, history.Labels)
}
return daemonSets, nil
}
...@@ -386,7 +386,7 @@ type DaemonSetUpdateStrategy struct { ...@@ -386,7 +386,7 @@ type DaemonSetUpdateStrategy struct {
// Rolling update config params. Present only if type = "RollingUpdate". // Rolling update config params. Present only if type = "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 Deployment `strategy.rollingUpdate`.
// See https://github.com/kubernetes/kubernetes/issues/35345 // See https://github.com/kubernetes/kubernetes/issues/35345
// +optional // +optional
RollingUpdate *RollingUpdateDaemonSet RollingUpdate *RollingUpdateDaemonSet
...@@ -449,10 +449,17 @@ type DaemonSetSpec struct { ...@@ -449,10 +449,17 @@ type DaemonSetSpec struct {
// +optional // +optional
MinReadySeconds int32 MinReadySeconds int32
// DEPRECATED.
// A sequence number representing a specific generation of the template. // A sequence number representing a specific generation of the template.
// Populated by the system. It can be set only during the creation. // Populated by the system. It can be set only during the creation.
// +optional // +optional
TemplateGeneration int64 TemplateGeneration int64
// The number of old history to retain to allow rollback.
// This is a pointer to distinguish between explicit zero and not specified.
// Defaults to 10.
// +optional
RevisionHistoryLimit *int32
} }
// DaemonSetStatus represents the current status of a daemon set. // DaemonSetStatus represents the current status of a daemon set.
...@@ -492,6 +499,12 @@ type DaemonSetStatus struct { ...@@ -492,6 +499,12 @@ type DaemonSetStatus struct {
// (ready for at least spec.minReadySeconds) // (ready for at least spec.minReadySeconds)
// +optional // +optional
NumberUnavailable int32 NumberUnavailable int32
// Count of hash collisions for the DaemonSet. The DaemonSet controller
// uses this field as a collision avoidance mechanism when it needs to
// create the name for the newest ControllerRevision.
// +optional
CollisionCount *int64
} }
// +genclient=true // +genclient=true
...@@ -519,10 +532,16 @@ type DaemonSet struct { ...@@ -519,10 +532,16 @@ type DaemonSet struct {
} }
const ( const (
// DEPRECATED: DefaultDaemonSetUniqueLabelKey is used instead.
// DaemonSetTemplateGenerationKey is the key of the labels that is added // DaemonSetTemplateGenerationKey is the key of the labels that is added
// to daemon set pods to distinguish between old and new pod templates // to daemon set pods to distinguish between old and new pod templates
// during DaemonSet template update. // during DaemonSet template update.
DaemonSetTemplateGenerationKey string = "pod-template-generation" DaemonSetTemplateGenerationKey string = "pod-template-generation"
// DefaultDaemonSetUniqueLabelKey is the default label key that is added
// to existing DaemonSet pods to distinguish between old and new
// DaemonSet pods during DaemonSet template updates.
DefaultDaemonSetUniqueLabelKey string = "daemonset-controller-hash"
) )
// DaemonSetList is a collection of daemon sets. // DaemonSetList is a collection of daemon sets.
......
...@@ -56,6 +56,10 @@ func SetDefaults_DaemonSet(obj *DaemonSet) { ...@@ -56,6 +56,10 @@ func SetDefaults_DaemonSet(obj *DaemonSet) {
updateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable updateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable
} }
} }
if obj.Spec.RevisionHistoryLimit == nil {
obj.Spec.RevisionHistoryLimit = new(int32)
*obj.Spec.RevisionHistoryLimit = 10
}
} }
func SetDefaults_Deployment(obj *Deployment) { func SetDefaults_Deployment(obj *Deployment) {
......
...@@ -123,10 +123,17 @@ message DaemonSetSpec { ...@@ -123,10 +123,17 @@ message DaemonSetSpec {
// +optional // +optional
optional int32 minReadySeconds = 4; optional int32 minReadySeconds = 4;
// DEPRECATED.
// A sequence number representing a specific generation of the template. // A sequence number representing a specific generation of the template.
// Populated by the system. It can be set only during the creation. // Populated by the system. It can be set only during the creation.
// +optional // +optional
optional int64 templateGeneration = 5; optional int64 templateGeneration = 5;
// The number of old history to retain to allow rollback.
// This is a pointer to distinguish between explicit zero and not specified.
// Defaults to 10.
// +optional
optional int32 revisionHistoryLimit = 6;
} }
// DaemonSetStatus represents the current status of a daemon set. // DaemonSetStatus represents the current status of a daemon set.
...@@ -169,6 +176,12 @@ message DaemonSetStatus { ...@@ -169,6 +176,12 @@ message DaemonSetStatus {
// (ready for at least spec.minReadySeconds) // (ready for at least spec.minReadySeconds)
// +optional // +optional
optional int32 numberUnavailable = 8; optional int32 numberUnavailable = 8;
// Count of hash collisions for the DaemonSet. The DaemonSet controller
// uses this field as a collision avoidance mechanism when it needs to
// create the name for the newest ControllerRevision.
// +optional
optional int64 collisionCount = 9;
} }
message DaemonSetUpdateStrategy { message DaemonSetUpdateStrategy {
...@@ -180,7 +193,7 @@ message DaemonSetUpdateStrategy { ...@@ -180,7 +193,7 @@ message DaemonSetUpdateStrategy {
// Rolling update config params. Present only if type = "RollingUpdate". // Rolling update config params. Present only if type = "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 Deployment `strategy.rollingUpdate`.
// See https://github.com/kubernetes/kubernetes/issues/35345 // See https://github.com/kubernetes/kubernetes/issues/35345
// +optional // +optional
optional RollingUpdateDaemonSet rollingUpdate = 2; optional RollingUpdateDaemonSet rollingUpdate = 2;
......
...@@ -386,7 +386,7 @@ type DaemonSetUpdateStrategy struct { ...@@ -386,7 +386,7 @@ type DaemonSetUpdateStrategy struct {
// Rolling update config params. Present only if type = "RollingUpdate". // Rolling update config params. Present only if type = "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 Deployment `strategy.rollingUpdate`.
// See https://github.com/kubernetes/kubernetes/issues/35345 // See https://github.com/kubernetes/kubernetes/issues/35345
// +optional // +optional
RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"` RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"`
...@@ -449,10 +449,17 @@ type DaemonSetSpec struct { ...@@ -449,10 +449,17 @@ type DaemonSetSpec struct {
// +optional // +optional
MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"`
// DEPRECATED.
// A sequence number representing a specific generation of the template. // A sequence number representing a specific generation of the template.
// Populated by the system. It can be set only during the creation. // Populated by the system. It can be set only during the creation.
// +optional // +optional
TemplateGeneration int64 `json:"templateGeneration,omitempty" protobuf:"varint,5,opt,name=templateGeneration"` TemplateGeneration int64 `json:"templateGeneration,omitempty" protobuf:"varint,5,opt,name=templateGeneration"`
// The number of old history to retain to allow rollback.
// This is a pointer to distinguish between explicit zero and not specified.
// Defaults to 10.
// +optional
RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"`
} }
// DaemonSetStatus represents the current status of a daemon set. // DaemonSetStatus represents the current status of a daemon set.
...@@ -495,6 +502,12 @@ type DaemonSetStatus struct { ...@@ -495,6 +502,12 @@ type DaemonSetStatus struct {
// (ready for at least spec.minReadySeconds) // (ready for at least spec.minReadySeconds)
// +optional // +optional
NumberUnavailable int32 `json:"numberUnavailable,omitempty" protobuf:"varint,8,opt,name=numberUnavailable"` NumberUnavailable int32 `json:"numberUnavailable,omitempty" protobuf:"varint,8,opt,name=numberUnavailable"`
// Count of hash collisions for the DaemonSet. The DaemonSet controller
// uses this field as a collision avoidance mechanism when it needs to
// create the name for the newest ControllerRevision.
// +optional
CollisionCount *int64 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"`
} }
// +genclient=true // +genclient=true
...@@ -522,10 +535,16 @@ type DaemonSet struct { ...@@ -522,10 +535,16 @@ type DaemonSet struct {
} }
const ( const (
// DEPRECATED: DefaultDaemonSetUniqueLabelKey is used instead.
// DaemonSetTemplateGenerationKey is the key of the labels that is added // DaemonSetTemplateGenerationKey is the key of the labels that is added
// to daemon set pods to distinguish between old and new pod templates // to daemon set pods to distinguish between old and new pod templates
// during DaemonSet template update. // during DaemonSet template update.
DaemonSetTemplateGenerationKey string = "pod-template-generation" DaemonSetTemplateGenerationKey string = "pod-template-generation"
// DefaultDaemonSetUniqueLabelKey is the default label key that is added
// to existing DaemonSet pods to distinguish between old and new
// DaemonSet pods during DaemonSet template updates.
DefaultDaemonSetUniqueLabelKey string = "daemonset-controller-hash"
) )
// DaemonSetList is a collection of daemon sets. // DaemonSetList is a collection of daemon sets.
......
...@@ -77,12 +77,13 @@ func (DaemonSetList) SwaggerDoc() map[string]string { ...@@ -77,12 +77,13 @@ func (DaemonSetList) SwaggerDoc() map[string]string {
} }
var map_DaemonSetSpec = map[string]string{ var map_DaemonSetSpec = map[string]string{
"": "DaemonSetSpec is the specification of a daemon set.", "": "DaemonSetSpec is the specification of a daemon set.",
"selector": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", "selector": "A label query over pods that are managed by the daemon set. Must match in order to be controlled. If empty, defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors",
"template": "An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template's node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", "template": "An object that describes the pod that will be created. The DaemonSet will create exactly one copy of this pod on every node that matches the template's node selector (or on every node if no node selector is specified). More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template",
"updateStrategy": "An update strategy to replace existing DaemonSet pods with new pods.", "updateStrategy": "An update strategy to replace existing DaemonSet pods with new pods.",
"minReadySeconds": "The 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).", "minReadySeconds": "The 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).",
"templateGeneration": "A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.", "templateGeneration": "DEPRECATED. A sequence number representing a specific generation of the template. Populated by the system. It can be set only during the creation.",
"revisionHistoryLimit": "The number of old history to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.",
} }
func (DaemonSetSpec) SwaggerDoc() map[string]string { func (DaemonSetSpec) SwaggerDoc() map[string]string {
...@@ -99,6 +100,7 @@ var map_DaemonSetStatus = map[string]string{ ...@@ -99,6 +100,7 @@ var map_DaemonSetStatus = map[string]string{
"updatedNumberScheduled": "The total number of nodes that are running updated daemon pod", "updatedNumberScheduled": "The total number of nodes that are running updated daemon pod",
"numberAvailable": "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 spec.minReadySeconds)", "numberAvailable": "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 spec.minReadySeconds)",
"numberUnavailable": "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 spec.minReadySeconds)", "numberUnavailable": "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 spec.minReadySeconds)",
"collisionCount": "Count of hash collisions for the DaemonSet. The DaemonSet controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ControllerRevision.",
} }
func (DaemonSetStatus) SwaggerDoc() map[string]string { func (DaemonSetStatus) SwaggerDoc() map[string]string {
......
...@@ -352,6 +352,7 @@ func autoConvert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *DaemonSet ...@@ -352,6 +352,7 @@ func autoConvert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec(in *DaemonSet
} }
out.MinReadySeconds = in.MinReadySeconds out.MinReadySeconds = in.MinReadySeconds
out.TemplateGeneration = in.TemplateGeneration out.TemplateGeneration = in.TemplateGeneration
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
return nil return nil
} }
...@@ -370,6 +371,7 @@ func autoConvert_extensions_DaemonSetSpec_To_v1beta1_DaemonSetSpec(in *extension ...@@ -370,6 +371,7 @@ func autoConvert_extensions_DaemonSetSpec_To_v1beta1_DaemonSetSpec(in *extension
} }
out.MinReadySeconds = in.MinReadySeconds out.MinReadySeconds = in.MinReadySeconds
out.TemplateGeneration = in.TemplateGeneration out.TemplateGeneration = in.TemplateGeneration
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
return nil return nil
} }
...@@ -387,6 +389,7 @@ func autoConvert_v1beta1_DaemonSetStatus_To_extensions_DaemonSetStatus(in *Daemo ...@@ -387,6 +389,7 @@ func autoConvert_v1beta1_DaemonSetStatus_To_extensions_DaemonSetStatus(in *Daemo
out.UpdatedNumberScheduled = in.UpdatedNumberScheduled out.UpdatedNumberScheduled = in.UpdatedNumberScheduled
out.NumberAvailable = in.NumberAvailable out.NumberAvailable = in.NumberAvailable
out.NumberUnavailable = in.NumberUnavailable out.NumberUnavailable = in.NumberUnavailable
out.CollisionCount = (*int64)(unsafe.Pointer(in.CollisionCount))
return nil return nil
} }
...@@ -404,6 +407,7 @@ func autoConvert_extensions_DaemonSetStatus_To_v1beta1_DaemonSetStatus(in *exten ...@@ -404,6 +407,7 @@ func autoConvert_extensions_DaemonSetStatus_To_v1beta1_DaemonSetStatus(in *exten
out.UpdatedNumberScheduled = in.UpdatedNumberScheduled out.UpdatedNumberScheduled = in.UpdatedNumberScheduled
out.NumberAvailable = in.NumberAvailable out.NumberAvailable = in.NumberAvailable
out.NumberUnavailable = in.NumberUnavailable out.NumberUnavailable = in.NumberUnavailable
out.CollisionCount = (*int64)(unsafe.Pointer(in.CollisionCount))
return nil return nil
} }
......
...@@ -182,6 +182,9 @@ func DeepCopy_v1beta1_DaemonSet(in interface{}, out interface{}, c *conversion.C ...@@ -182,6 +182,9 @@ func DeepCopy_v1beta1_DaemonSet(in interface{}, out interface{}, c *conversion.C
if err := DeepCopy_v1beta1_DaemonSetSpec(&in.Spec, &out.Spec, c); err != nil { if err := DeepCopy_v1beta1_DaemonSetSpec(&in.Spec, &out.Spec, c); err != nil {
return err return err
} }
if err := DeepCopy_v1beta1_DaemonSetStatus(&in.Status, &out.Status, c); err != nil {
return err
}
return nil return nil
} }
} }
...@@ -225,6 +228,11 @@ func DeepCopy_v1beta1_DaemonSetSpec(in interface{}, out interface{}, c *conversi ...@@ -225,6 +228,11 @@ func DeepCopy_v1beta1_DaemonSetSpec(in interface{}, out interface{}, c *conversi
if err := DeepCopy_v1beta1_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, c); err != nil { if err := DeepCopy_v1beta1_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, c); err != nil {
return err return err
} }
if in.RevisionHistoryLimit != nil {
in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit
*out = new(int32)
**out = **in
}
return nil return nil
} }
} }
...@@ -235,6 +243,11 @@ func DeepCopy_v1beta1_DaemonSetStatus(in interface{}, out interface{}, c *conver ...@@ -235,6 +243,11 @@ func DeepCopy_v1beta1_DaemonSetStatus(in interface{}, out interface{}, c *conver
in := in.(*DaemonSetStatus) in := in.(*DaemonSetStatus)
out := out.(*DaemonSetStatus) out := out.(*DaemonSetStatus)
*out = *in *out = *in
if in.CollisionCount != nil {
in, out := &in.CollisionCount, &out.CollisionCount
*out = new(int64)
**out = **in
}
return nil return nil
} }
} }
......
...@@ -183,6 +183,9 @@ func DeepCopy_extensions_DaemonSet(in interface{}, out interface{}, c *conversio ...@@ -183,6 +183,9 @@ func DeepCopy_extensions_DaemonSet(in interface{}, out interface{}, c *conversio
if err := DeepCopy_extensions_DaemonSetSpec(&in.Spec, &out.Spec, c); err != nil { if err := DeepCopy_extensions_DaemonSetSpec(&in.Spec, &out.Spec, c); err != nil {
return err return err
} }
if err := DeepCopy_extensions_DaemonSetStatus(&in.Status, &out.Status, c); err != nil {
return err
}
return nil return nil
} }
} }
...@@ -226,6 +229,11 @@ func DeepCopy_extensions_DaemonSetSpec(in interface{}, out interface{}, c *conve ...@@ -226,6 +229,11 @@ func DeepCopy_extensions_DaemonSetSpec(in interface{}, out interface{}, c *conve
if err := DeepCopy_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, c); err != nil { if err := DeepCopy_extensions_DaemonSetUpdateStrategy(&in.UpdateStrategy, &out.UpdateStrategy, c); err != nil {
return err return err
} }
if in.RevisionHistoryLimit != nil {
in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit
*out = new(int32)
**out = **in
}
return nil return nil
} }
} }
...@@ -236,6 +244,11 @@ func DeepCopy_extensions_DaemonSetStatus(in interface{}, out interface{}, c *con ...@@ -236,6 +244,11 @@ func DeepCopy_extensions_DaemonSetStatus(in interface{}, out interface{}, c *con
in := in.(*DaemonSetStatus) in := in.(*DaemonSetStatus)
out := out.(*DaemonSetStatus) out := out.(*DaemonSetStatus)
*out = *in *out = *in
if in.CollisionCount != nil {
in, out := &in.CollisionCount, &out.CollisionCount
*out = new(int64)
**out = **in
}
return nil return nil
} }
} }
......
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
podutil "k8s.io/kubernetes/pkg/api/v1/pod" podutil "k8s.io/kubernetes/pkg/api/v1/pod"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
extensionsinternal "k8s.io/kubernetes/pkg/apis/extensions" extensionsinternal "k8s.io/kubernetes/pkg/apis/extensions"
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
...@@ -260,9 +261,19 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() { ...@@ -260,9 +261,19 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start") Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start")
By("Make sure all daemon pods have correct template generation 1") By("Make sure all daemon pods have correct template generation 1")
templateGeneration := "1"
err = checkDaemonPodsTemplateGeneration(c, ns, label, "1") err = checkDaemonPodsTemplateGeneration(c, ns, label, "1")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Check history and labels
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
first := curHistory(listDaemonHistories(c, ns, label), &ds.Spec.Template)
firstHash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
Expect(first.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(firstHash))
Expect(first.Revision).To(Equal(int64(1)))
checkDaemonSetPodsLabels(listDaemonPods(c, ns, label), firstHash, templateGeneration)
By("Update daemon pods image.") By("Update daemon pods image.")
patch := getDaemonSetImagePatch(ds.Spec.Template.Spec.Containers[0].Name, redisImage) patch := getDaemonSetImagePatch(ds.Spec.Template.Spec.Containers[0].Name, redisImage)
ds, err = c.Extensions().DaemonSets(ns).Patch(dsName, types.StrategicMergePatchType, []byte(patch)) ds, err = c.Extensions().DaemonSets(ns).Patch(dsName, types.StrategicMergePatchType, []byte(patch))
...@@ -274,12 +285,21 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() { ...@@ -274,12 +285,21 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
By("Make sure all daemon pods have correct template generation 1") By("Make sure all daemon pods have correct template generation 1")
err = checkDaemonPodsTemplateGeneration(c, ns, label, "1") err = checkDaemonPodsTemplateGeneration(c, ns, label, templateGeneration)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
By("Check that daemon pods are still running on every node of the cluster.") By("Check that daemon pods are still running on every node of the cluster.")
err = wait.PollImmediate(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, ds)) err = wait.PollImmediate(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, ds))
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start") Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start")
// Check history and labels
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
cur := curHistory(listDaemonHistories(c, ns, label), &ds.Spec.Template)
curHash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(curHash))
Expect(cur.Revision).To(Equal(int64(2)))
checkDaemonSetPodsLabels(listDaemonPods(c, ns, label), firstHash, templateGeneration)
}) })
It("Should update pod when spec was updated and update strategy is RollingUpdate", func() { It("Should update pod when spec was updated and update strategy is RollingUpdate", func() {
...@@ -302,23 +322,42 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() { ...@@ -302,23 +322,42 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
err = checkDaemonPodsTemplateGeneration(c, ns, label, fmt.Sprint(templateGeneration)) err = checkDaemonPodsTemplateGeneration(c, ns, label, fmt.Sprint(templateGeneration))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Check history and labels
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
cur := curHistory(listDaemonHistories(c, ns, label), &ds.Spec.Template)
hash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(hash))
Expect(cur.Revision).To(Equal(int64(1)))
checkDaemonSetPodsLabels(listDaemonPods(c, ns, label), hash, fmt.Sprint(templateGeneration))
By("Update daemon pods image.") By("Update daemon pods image.")
patch := getDaemonSetImagePatch(ds.Spec.Template.Spec.Containers[0].Name, redisImage) patch := getDaemonSetImagePatch(ds.Spec.Template.Spec.Containers[0].Name, redisImage)
ds, err = c.Extensions().DaemonSets(ns).Patch(dsName, types.StrategicMergePatchType, []byte(patch)) ds, err = c.Extensions().DaemonSets(ns).Patch(dsName, types.StrategicMergePatchType, []byte(patch))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(ds.Spec.TemplateGeneration).To(Equal(templateGeneration + 1)) templateGeneration++
Expect(ds.Spec.TemplateGeneration).To(Equal(templateGeneration))
By("Check that daemon pods images are updated.") By("Check that daemon pods images are updated.")
err = wait.PollImmediate(dsRetryPeriod, dsRetryTimeout, checkDaemonPodsImageAndAvailability(c, ds, redisImage, 1)) err = wait.PollImmediate(dsRetryPeriod, dsRetryTimeout, checkDaemonPodsImageAndAvailability(c, ds, redisImage, 1))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
By(fmt.Sprintf("Make sure all daemon pods have correct template generation %d", templateGeneration+1)) By(fmt.Sprintf("Make sure all daemon pods have correct template generation %d", templateGeneration))
err = checkDaemonPodsTemplateGeneration(c, ns, label, fmt.Sprint(templateGeneration+1)) err = checkDaemonPodsTemplateGeneration(c, ns, label, fmt.Sprint(templateGeneration))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
By("Check that daemon pods are still running on every node of the cluster.") By("Check that daemon pods are still running on every node of the cluster.")
err = wait.PollImmediate(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, ds)) err = wait.PollImmediate(dsRetryPeriod, dsRetryTimeout, checkRunningOnAllNodes(f, ds))
Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start") Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to start")
// Check history and labels
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
cur = curHistory(listDaemonHistories(c, ns, label), &ds.Spec.Template)
hash = ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(hash))
Expect(cur.Revision).To(Equal(int64(2)))
checkDaemonSetPodsLabels(listDaemonPods(c, ns, label), hash, fmt.Sprint(templateGeneration))
}) })
It("Should adopt or recreate existing pods when creating a RollingUpdate DaemonSet with matching or mismatching templateGeneration", func() { It("Should adopt or recreate existing pods when creating a RollingUpdate DaemonSet with matching or mismatching templateGeneration", func() {
...@@ -698,3 +737,44 @@ func checkDaemonSetPodsName(c clientset.Interface, ns, prefix string, label map[ ...@@ -698,3 +737,44 @@ func checkDaemonSetPodsName(c clientset.Interface, ns, prefix string, label map[
} }
return nil return nil
} }
func checkDaemonSetPodsLabels(podList *v1.PodList, hash, templateGeneration string) {
for _, pod := range podList.Items {
podHash := pod.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
podTemplate := pod.Labels[extensions.DaemonSetTemplateGenerationKey]
Expect(len(podHash)).To(BeNumerically(">", 0))
if len(hash) > 0 {
Expect(podHash).To(Equal(hash))
}
Expect(len(podTemplate)).To(BeNumerically(">", 0))
Expect(podTemplate).To(Equal(templateGeneration))
}
}
func listDaemonHistories(c clientset.Interface, ns string, label map[string]string) *apps.ControllerRevisionList {
selector := labels.Set(label).AsSelector()
options := metav1.ListOptions{LabelSelector: selector.String()}
historyList, err := c.Apps().ControllerRevisions(ns).List(options)
Expect(err).NotTo(HaveOccurred())
Expect(len(historyList.Items)).To(BeNumerically(">", 0))
return historyList
}
func curHistory(historyList *apps.ControllerRevisionList, template *v1.PodTemplateSpec) *apps.ControllerRevision {
var curHistory *apps.ControllerRevision
foundCurHistories := 0
for i := range historyList.Items {
history := &historyList.Items[i]
// Every history should have the hash label
Expect(len(history.Labels[extensions.DefaultDaemonSetUniqueLabelKey])).To(BeNumerically(">", 0))
match, err := daemon.Match(template, history)
Expect(err).NotTo(HaveOccurred())
if match {
curHistory = history
foundCurHistories++
}
}
Expect(foundCurHistories).To(Equal(1))
Expect(curHistory).NotTo(BeNil())
return curHistory
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment