Merge pull request #47075 from janetkuo/ds-history-patch

Automatic merge from submit-queue

Change what is stored in DaemonSet history `.data`

**What this PR does / why we need it**: 
In DaemonSet history `.data`, store a strategic merge patch that can be applied to restore a DaemonSet. Only PodSpecTemplate is saved. 

This will become consistent with the data stored in StatefulSet history. 

Before this fix, a serialized pod template is stored in `.data`; however, seriazlized pod template isn't a `runtime.RawExtension`, and caused problems when controllers try to patch the history's controller ref. 

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #47008

**Special notes for your reviewer**: @kubernetes/sig-apps-bugs @erictune @kow3ns @kargakis @lukaszo @mengqiy 

**Release note**:

```release-note
NONE
```
pull/6/head
Kubernetes Submit Queue 2017-06-12 23:31:08 -07:00 committed by GitHub
commit aa35738a21
9 changed files with 131 additions and 90 deletions

View File

@ -43,6 +43,7 @@ IMAGE_DEPLOYMENT_R2="$IMAGE_NGINX" # deployment-revision2.yaml
IMAGE_PERL="gcr.io/google-containers/perl"
IMAGE_DAEMONSET_R1="gcr.io/google-containers/pause:2.0"
IMAGE_DAEMONSET_R2="gcr.io/google-containers/pause:latest"
IMAGE_DAEMONSET_R2_2="gcr.io/google-containers/nginx:test-cmd" # rollingupdate-daemonset-rv2.yaml
# Expose kubectl directly for readability
PATH="${KUBE_OUTPUT_HOSTBIN}":$PATH
@ -2900,23 +2901,32 @@ run_daemonset_history_tests() {
kubectl apply -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
# Rollback to revision 1 - should be no-op
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]}"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field0}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$container_len}}{{end}}" "1"
# Update the DaemonSet (revision 2)
kubectl apply -f hack/testdata/rollingupdate-daemonset-rv2.yaml "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field0}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field1}}:{{end}}" "${IMAGE_DAEMONSET_R2_2}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$container_len}}{{end}}" "2"
# Rollback to revision 1 with dry-run - should be no-op
kubectl rollout undo daemonset --dry-run=true "${kube_flags[@]}"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field0}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field1}}:{{end}}" "${IMAGE_DAEMONSET_R2_2}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$container_len}}{{end}}" "2"
# Rollback to revision 1
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field0}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$container_len}}{{end}}" "1"
# Rollback to revision 1000000 - should fail
output_message=$(! kubectl rollout undo daemonset --to-revision=1000000 "${kube_flags[@]}" 2>&1)
kube::test::if_has_string "${output_message}" "unable to find specified revision"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$daemonset_image_field0}}:{{end}}" "${IMAGE_DAEMONSET_R1}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$container_len}}{{end}}" "1"
# Rollback to last revision
kubectl rollout undo daemonset "${kube_flags[@]}"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field0}}:{{end}}" "${IMAGE_DAEMONSET_R2}:"
kube::test::wait_object_assert daemonset "{{range.items}}{{$daemonset_image_field1}}:{{end}}" "${IMAGE_DAEMONSET_R2_2}:"
kube::test::get_object_assert daemonset "{{range.items}}{{$container_len}}{{end}}" "2"
# Clean up
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
}
@ -3153,7 +3163,9 @@ runTests() {
pdb_min_available=".spec.minAvailable"
pdb_max_unavailable=".spec.maxUnavailable"
template_generation_field=".spec.templateGeneration"
daemonset_image_field="(index .spec.template.spec.containers 0).image"
container_len="(len .spec.template.spec.containers)"
daemonset_image_field0="(index .spec.template.spec.containers 0).image"
daemonset_image_field1="(index .spec.template.spec.containers 1).image"
# Make sure "default" namespace exists.
if kube::test::if_supports_resource "${namespaces}" ; then

View File

@ -25,3 +25,5 @@ spec:
containers:
- name: kubernetes-pause
image: gcr.io/google-containers/pause:latest
- name: app
image: gcr.io/google-containers/nginx:test-cmd

View File

@ -41,7 +41,6 @@ go_library(
"//plugin/pkg/scheduler/algorithm/predicates:go_default_library",
"//plugin/pkg/scheduler/schedulercache: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/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",

View File

@ -17,11 +17,12 @@ limitations under the License.
package daemon
import (
"bytes"
"fmt"
"sort"
"github.com/golang/glog"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
@ -103,7 +104,7 @@ func (dsc *DaemonSetsController) constructHistory(ds *extensions.DaemonSet) (cur
}
// Compare histories with ds to separate cur and old history
found := false
found, err = Match(&ds.Spec.Template, history)
found, err = Match(ds, history)
if err != nil {
return nil, nil, err
}
@ -294,24 +295,44 @@ func (dsc *DaemonSetsController) controlledHistories(ds *extensions.DaemonSet) (
return result, nil
}
// Match check if ds template is semantically equal to the template stored in history
func Match(template *v1.PodTemplateSpec, history *apps.ControllerRevision) (bool, error) {
t, err := DecodeHistory(history)
return apiequality.Semantic.DeepEqual(template, t), err
// Match check if the given DaemonSet's template matches the template stored in the given history.
func Match(ds *extensions.DaemonSet, history *apps.ControllerRevision) (bool, error) {
patch, err := getPatch(ds)
if err != nil {
return false, err
}
return bytes.Equal(patch, history.Data.Raw), nil
}
func DecodeHistory(history *apps.ControllerRevision) (*v1.PodTemplateSpec, error) {
template := v1.PodTemplateSpec{}
err := json.Unmarshal(history.Data.Raw, &template)
return &template, err
}
// getPatch returns a strategic merge patch that can be applied to restore a Daemonset to a
// previous version. If the returned error is nil the patch is valid. The current state that we save is just the
// PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously
// recorded patches.
func getPatch(ds *extensions.DaemonSet) ([]byte, error) {
dsBytes, err := json.Marshal(ds)
if err != nil {
return nil, err
}
var raw map[string]interface{}
err = json.Unmarshal(dsBytes, &raw)
if err != nil {
return nil, err
}
objCopy := make(map[string]interface{})
specCopy := make(map[string]interface{})
func encodeTemplate(template *v1.PodTemplateSpec) ([]byte, error) {
return json.Marshal(template)
// Create a patch of the DaemonSet that replaces spec.template
spec := raw["spec"].(map[string]interface{})
template := spec["template"].(map[string]interface{})
specCopy["template"] = template
template["$patch"] = "replace"
objCopy["spec"] = specCopy
patch, err := json.Marshal(objCopy)
return patch, err
}
func (dsc *DaemonSetsController) snapshot(ds *extensions.DaemonSet, revision int64) (*apps.ControllerRevision, error) {
encodedTemplate, err := encodeTemplate(&ds.Spec.Template)
patch, err := getPatch(ds)
if err != nil {
return nil, err
}
@ -325,7 +346,7 @@ func (dsc *DaemonSetsController) snapshot(ds *extensions.DaemonSet, revision int
Annotations: ds.Annotations,
OwnerReferences: []metav1.OwnerReference{*newControllerRef(ds)},
},
Data: runtime.RawExtension{Raw: encodedTemplate},
Data: runtime.RawExtension{Raw: patch},
Revision: revision,
}
@ -337,7 +358,7 @@ func (dsc *DaemonSetsController) snapshot(ds *extensions.DaemonSet, revision int
return nil, getErr
}
// Check if we already created it
done, err := Match(&ds.Spec.Template, existedHistory)
done, err := Match(ds, existedHistory)
if err != nil {
return nil, err
}

View File

@ -154,7 +154,6 @@ go_library(
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag: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/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
@ -167,7 +166,9 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/types: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/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",

View File

@ -26,14 +26,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/extensions"
extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
externalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/daemon"
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
sliceutil "k8s.io/kubernetes/pkg/util/slice"
@ -149,13 +152,10 @@ type DaemonSetHistoryViewer struct {
// ViewHistory returns a revision-to-history map as the revision history of a deployment
// TODO: this should be a describer
func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
ds, err := h.c.Extensions().DaemonSets(namespace).Get(name, metav1.GetOptions{})
versionedClient := versionedClientsetForDaemonSet(h.c)
ds, allHistory, err := controlledHistories(versionedClient, namespace, name)
if err != nil {
return "", fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err)
}
allHistory, err := controlledHistories(h.c, ds)
if err != nil {
return "", fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err)
return "", fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", name, err)
}
historyInfo := make(map[int64]*appsv1beta1.ControllerRevision)
for _, history := range allHistory {
@ -173,11 +173,11 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in
if !ok {
return "", fmt.Errorf("unable to find the specified revision")
}
template, err := daemon.DecodeHistory(history)
dsOfHistory, err := applyHistory(ds, history)
if err != nil {
return "", fmt.Errorf("unable to decode history %s", history.Name)
return "", fmt.Errorf("unable to parse history %s", history.Name)
}
return printTemplate(template)
return printTemplate(&dsOfHistory.Spec.Template)
}
// Print an overview of all Revisions
@ -255,17 +255,19 @@ func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision
}
// controlledHistories returns all ControllerRevisions controlled by the given DaemonSet
// TODO: Use external version DaemonSet instead when #3955 is fixed
func controlledHistories(c clientset.Interface, ds *extensions.DaemonSet) ([]*appsv1beta1.ControllerRevision, error) {
func controlledHistories(c externalclientset.Interface, namespace, name string) (*extensionsv1beta1.DaemonSet, []*appsv1beta1.ControllerRevision, error) {
ds, err := c.ExtensionsV1beta1().DaemonSets(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err)
}
var result []*appsv1beta1.ControllerRevision
selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
if err != nil {
return nil, err
return nil, nil, err
}
versionedClient := versionedClientsetForDaemonSet(c)
historyList, err := versionedClient.AppsV1beta1().ControllerRevisions(ds.Namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
historyList, err := c.AppsV1beta1().ControllerRevisions(ds.Namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return nil, err
return nil, nil, err
}
for i := range historyList.Items {
history := historyList.Items[i]
@ -275,7 +277,29 @@ func controlledHistories(c clientset.Interface, ds *extensions.DaemonSet) ([]*ap
}
result = append(result, &history)
}
return result, nil
return ds, result, nil
}
// applyHistory returns a specific revision of DaemonSet by applying the given history to a copy of the given DaemonSet
func applyHistory(ds *extensionsv1beta1.DaemonSet, history *appsv1beta1.ControllerRevision) (*extensionsv1beta1.DaemonSet, error) {
obj, err := api.Scheme.New(ds.GroupVersionKind())
if err != nil {
return nil, err
}
clone := obj.(*extensionsv1beta1.DaemonSet)
cloneBytes, err := json.Marshal(clone)
if err != nil {
return nil, err
}
patched, err := strategicpatch.StrategicMergePatch(cloneBytes, history.Data.Raw, clone)
if err != nil {
return nil, err
}
err = json.Unmarshal(patched, clone)
if err != nil {
return nil, err
}
return clone, nil
}
// TODO: copied here until this becomes a describer

View File

@ -24,10 +24,10 @@ import (
"sort"
"syscall"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
@ -36,7 +36,6 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
externalextensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/retry"
"k8s.io/kubernetes/pkg/controller/daemon"
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
@ -221,7 +220,8 @@ func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations ma
if !ok {
return "", fmt.Errorf("passed object is not a DaemonSet: %#v", obj)
}
allHistory, err := controlledHistories(r.c, ds)
versionedClient := versionedClientsetForDaemonSet(r.c)
versionedDS, allHistory, err := controlledHistories(versionedClient, ds.Namespace, ds.Name)
if err != nil {
return "", fmt.Errorf("unable to find history controlled by DaemonSet %s: %v", ds.Name, err)
}
@ -249,55 +249,36 @@ func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations ma
return "", revisionNotFoundErr(toRevision)
}
// Get the template of the history to rollback to
toTemplate, err := getInternalTemplate(toHistory)
if err != nil {
return "", err
}
if dryRun {
appliedDS, err := applyHistory(versionedDS, toHistory)
if err != nil {
return "", err
}
content := bytes.NewBuffer([]byte{})
w := printersinternal.NewPrefixWriter(content)
printersinternal.DescribePodTemplate(toTemplate, w)
internalTemplate := &api.PodTemplateSpec{}
if err := v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&appliedDS.Spec.Template, internalTemplate, nil); err != nil {
return "", fmt.Errorf("failed to convert podtemplate while printing: %v", err)
}
printersinternal.DescribePodTemplate(internalTemplate, w)
return fmt.Sprintf("will roll back to %s", content.String()), nil
}
// Update DaemonSet template, and retry on conflict
skipUpdate := false
retryErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
var err error
ds, err = r.c.Extensions().DaemonSets(ds.Namespace).Get(ds.Name, metav1.GetOptions{})
if err != nil {
return err
}
if apiequality.Semantic.DeepEqual(toTemplate, &ds.Spec.Template) {
skipUpdate = true
return nil
}
ds.Spec.Template = *toTemplate
_, err = r.c.Extensions().DaemonSets(ds.Namespace).Update(ds)
return err
})
if retryErr != nil {
return "", retryErr
// Skip if the revision already matches current DaemonSet
done, err := daemon.Match(versionedDS, toHistory)
if err != nil {
return "", err
}
if skipUpdate {
if done {
return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
}
return rollbackSuccess, nil
}
// Restore revision
if _, err = versionedClient.ExtensionsV1beta1().DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, toHistory.Data.Raw); err != nil {
return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
}
func getInternalTemplate(toHistory *appsv1beta1.ControllerRevision) (*api.PodTemplateSpec, error) {
template, err := daemon.DecodeHistory(toHistory)
if err != nil {
return nil, err
}
internalTemplate := &api.PodTemplateSpec{}
if err := v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(template, internalTemplate, nil); err != nil {
return nil, fmt.Errorf("failed to convert podtemplate, %v", err)
}
return internalTemplate, nil
return rollbackSuccess, nil
}
func revisionNotFoundErr(r int64) error {

View File

@ -39,6 +39,7 @@ func versionedClientsetForDaemonSet(internalClient internalclientset.Interface)
return &externalclientset.Clientset{}
}
return &externalclientset.Clientset{
AppsV1beta1Client: apps.New(internalClient.Apps().RESTClient()),
AppsV1beta1Client: apps.New(internalClient.Apps().RESTClient()),
ExtensionsV1beta1Client: extensions.New(internalClient.Extensions().RESTClient()),
}
}

View File

@ -268,7 +268,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
// 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)
first := curHistory(listDaemonHistories(c, ns, label), ds)
firstHash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
Expect(first.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(firstHash))
Expect(first.Revision).To(Equal(int64(1)))
@ -295,7 +295,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
// 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)
cur := curHistory(listDaemonHistories(c, ns, label), ds)
curHash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(curHash))
Expect(cur.Revision).To(Equal(int64(2)))
@ -325,7 +325,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
// 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)
cur := curHistory(listDaemonHistories(c, ns, label), ds)
hash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(hash))
Expect(cur.Revision).To(Equal(int64(1)))
@ -353,7 +353,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
// 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)
cur = curHistory(listDaemonHistories(c, ns, label), ds)
hash = ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(hash))
Expect(cur.Revision).To(Equal(int64(2)))
@ -760,14 +760,14 @@ func listDaemonHistories(c clientset.Interface, ns string, label map[string]stri
return historyList
}
func curHistory(historyList *apps.ControllerRevisionList, template *v1.PodTemplateSpec) *apps.ControllerRevision {
func curHistory(historyList *apps.ControllerRevisionList, ds *extensions.DaemonSet) *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)
match, err := daemon.Match(ds, history)
Expect(err).NotTo(HaveOccurred())
if match {
curHistory = history