mirror of https://github.com/k3s-io/k3s
Update kubectl rollout to consume `.data` of DaemonSet history
Also update tset data to make sure DaemonSet template is replaced, not merged, when rolling back.pull/6/head
parent
f9070b6ace
commit
2b8f91e549
|
@ -43,6 +43,7 @@ IMAGE_DEPLOYMENT_R2="$IMAGE_NGINX" # deployment-revision2.yaml
|
||||||
IMAGE_PERL="gcr.io/google-containers/perl"
|
IMAGE_PERL="gcr.io/google-containers/perl"
|
||||||
IMAGE_DAEMONSET_R1="gcr.io/google-containers/pause:2.0"
|
IMAGE_DAEMONSET_R1="gcr.io/google-containers/pause:2.0"
|
||||||
IMAGE_DAEMONSET_R2="gcr.io/google-containers/pause:latest"
|
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
|
# Expose kubectl directly for readability
|
||||||
PATH="${KUBE_OUTPUT_HOSTBIN}":$PATH
|
PATH="${KUBE_OUTPUT_HOSTBIN}":$PATH
|
||||||
|
@ -2900,23 +2901,32 @@ run_daemonset_history_tests() {
|
||||||
kubectl apply -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
|
kubectl apply -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
|
||||||
# Rollback to revision 1 - should be no-op
|
# Rollback to revision 1 - should be no-op
|
||||||
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]}"
|
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)
|
# Update the DaemonSet (revision 2)
|
||||||
kubectl apply -f hack/testdata/rollingupdate-daemonset-rv2.yaml "${kube_flags[@]}"
|
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
|
# Rollback to revision 1 with dry-run - should be no-op
|
||||||
kubectl rollout undo daemonset --dry-run=true "${kube_flags[@]}"
|
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
|
# Rollback to revision 1
|
||||||
kubectl rollout undo daemonset --to-revision=1 "${kube_flags[@]}"
|
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
|
# Rollback to revision 1000000 - should fail
|
||||||
output_message=$(! kubectl rollout undo daemonset --to-revision=1000000 "${kube_flags[@]}" 2>&1)
|
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::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
|
# Rollback to last revision
|
||||||
kubectl rollout undo daemonset "${kube_flags[@]}"
|
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
|
# Clean up
|
||||||
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
|
kubectl delete -f hack/testdata/rollingupdate-daemonset.yaml "${kube_flags[@]}"
|
||||||
}
|
}
|
||||||
|
@ -3153,7 +3163,9 @@ runTests() {
|
||||||
pdb_min_available=".spec.minAvailable"
|
pdb_min_available=".spec.minAvailable"
|
||||||
pdb_max_unavailable=".spec.maxUnavailable"
|
pdb_max_unavailable=".spec.maxUnavailable"
|
||||||
template_generation_field=".spec.templateGeneration"
|
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.
|
# Make sure "default" namespace exists.
|
||||||
if kube::test::if_supports_resource "${namespaces}" ; then
|
if kube::test::if_supports_resource "${namespaces}" ; then
|
||||||
|
|
|
@ -25,3 +25,5 @@ spec:
|
||||||
containers:
|
containers:
|
||||||
- name: kubernetes-pause
|
- name: kubernetes-pause
|
||||||
image: gcr.io/google-containers/pause:latest
|
image: gcr.io/google-containers/pause:latest
|
||||||
|
- name: app
|
||||||
|
image: gcr.io/google-containers/nginx:test-cmd
|
||||||
|
|
|
@ -41,7 +41,6 @@ go_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",
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
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"
|
||||||
|
@ -38,8 +39,6 @@ import (
|
||||||
labelsutil "k8s.io/kubernetes/pkg/util/labels"
|
labelsutil "k8s.io/kubernetes/pkg/util/labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
var patchCodec = api.Codecs.LegacyCodec(extensions.SchemeGroupVersion)
|
|
||||||
|
|
||||||
// rollingUpdate deletes old daemon set pods making sure that no more than
|
// rollingUpdate deletes old daemon set pods making sure that no more than
|
||||||
// ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable pods are unavailable
|
// ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable pods are unavailable
|
||||||
func (dsc *DaemonSetsController) rollingUpdate(ds *extensions.DaemonSet, hash string) error {
|
func (dsc *DaemonSetsController) rollingUpdate(ds *extensions.DaemonSet, hash string) error {
|
||||||
|
@ -305,25 +304,24 @@ func Match(ds *extensions.DaemonSet, history *apps.ControllerRevision) (bool, er
|
||||||
return bytes.Equal(patch, history.Data.Raw), nil
|
return bytes.Equal(patch, history.Data.Raw), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeHistory(history *apps.ControllerRevision) (*v1.PodTemplateSpec, error) {
|
|
||||||
template := v1.PodTemplate{}
|
|
||||||
err := json.Unmarshal(history.Data.Raw, &template)
|
|
||||||
return &template.Template, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPatch returns a strategic merge patch that can be applied to restore a Daemonset to a
|
// 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
|
// 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
|
// PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously
|
||||||
// recorded patches.
|
// recorded patches.
|
||||||
func getPatch(ds *extensions.DaemonSet) ([]byte, error) {
|
func getPatch(ds *extensions.DaemonSet) ([]byte, error) {
|
||||||
str, err := runtime.Encode(patchCodec, ds)
|
dsBytes, err := json.Marshal(ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var raw map[string]interface{}
|
var raw map[string]interface{}
|
||||||
json.Unmarshal([]byte(str), &raw)
|
err = json.Unmarshal(dsBytes, &raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
objCopy := make(map[string]interface{})
|
objCopy := make(map[string]interface{})
|
||||||
specCopy := make(map[string]interface{})
|
specCopy := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Create a patch of the DaemonSet that replaces spec.template
|
||||||
spec := raw["spec"].(map[string]interface{})
|
spec := raw["spec"].(map[string]interface{})
|
||||||
template := spec["template"].(map[string]interface{})
|
template := spec["template"].(map[string]interface{})
|
||||||
specCopy["template"] = template
|
specCopy["template"] = template
|
||||||
|
|
|
@ -154,7 +154,6 @@ go_library(
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||||
"//vendor/github.com/spf13/pflag: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/errors:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/resource: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/types: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/json:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/sets: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/uuid:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
|
|
|
@ -26,14 +26,17 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"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"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
"k8s.io/kubernetes/pkg/apis/apps"
|
"k8s.io/kubernetes/pkg/apis/apps"
|
||||||
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
|
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"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"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/controller"
|
"k8s.io/kubernetes/pkg/controller"
|
||||||
"k8s.io/kubernetes/pkg/controller/daemon"
|
|
||||||
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
|
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
sliceutil "k8s.io/kubernetes/pkg/util/slice"
|
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
|
// ViewHistory returns a revision-to-history map as the revision history of a deployment
|
||||||
// TODO: this should be a describer
|
// TODO: this should be a describer
|
||||||
func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to retrieve DaemonSet %s: %v", name, err)
|
return "", fmt.Errorf("unable to find history controlled by 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)
|
|
||||||
}
|
}
|
||||||
historyInfo := make(map[int64]*appsv1beta1.ControllerRevision)
|
historyInfo := make(map[int64]*appsv1beta1.ControllerRevision)
|
||||||
for _, history := range allHistory {
|
for _, history := range allHistory {
|
||||||
|
@ -173,11 +173,11 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("unable to find the specified revision")
|
return "", fmt.Errorf("unable to find the specified revision")
|
||||||
}
|
}
|
||||||
template, err := daemon.DecodeHistory(history)
|
dsOfHistory, err := applyHistory(ds, history)
|
||||||
if err != nil {
|
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
|
// 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
|
// controlledHistories returns all ControllerRevisions controlled by the given DaemonSet
|
||||||
// TODO: Use external version DaemonSet instead when #3955 is fixed
|
func controlledHistories(c externalclientset.Interface, namespace, name string) (*extensionsv1beta1.DaemonSet, []*appsv1beta1.ControllerRevision, error) {
|
||||||
func controlledHistories(c clientset.Interface, ds *extensions.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
|
var result []*appsv1beta1.ControllerRevision
|
||||||
selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
|
selector, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
versionedClient := versionedClientsetForDaemonSet(c)
|
historyList, err := c.AppsV1beta1().ControllerRevisions(ds.Namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
|
||||||
historyList, err := versionedClient.AppsV1beta1().ControllerRevisions(ds.Namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for i := range historyList.Items {
|
for i := range historyList.Items {
|
||||||
history := historyList.Items[i]
|
history := historyList.Items[i]
|
||||||
|
@ -275,7 +277,29 @@ func controlledHistories(c clientset.Interface, ds *extensions.DaemonSet) ([]*ap
|
||||||
}
|
}
|
||||||
result = append(result, &history)
|
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
|
// TODO: copied here until this becomes a describer
|
||||||
|
|
|
@ -24,10 +24,10 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
@ -36,7 +36,6 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
externalextensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
externalextensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/client/retry"
|
|
||||||
"k8s.io/kubernetes/pkg/controller/daemon"
|
"k8s.io/kubernetes/pkg/controller/daemon"
|
||||||
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
|
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
|
@ -221,7 +220,8 @@ func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations ma
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("passed object is not a DaemonSet: %#v", obj)
|
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 {
|
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", ds.Name, err)
|
||||||
}
|
}
|
||||||
|
@ -249,55 +249,36 @@ func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations ma
|
||||||
return "", revisionNotFoundErr(toRevision)
|
return "", revisionNotFoundErr(toRevision)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the template of the history to rollback to
|
|
||||||
toTemplate, err := getInternalTemplate(toHistory)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if dryRun {
|
if dryRun {
|
||||||
|
appliedDS, err := applyHistory(versionedDS, toHistory)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
content := bytes.NewBuffer([]byte{})
|
content := bytes.NewBuffer([]byte{})
|
||||||
w := printersinternal.NewPrefixWriter(content)
|
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
|
return fmt.Sprintf("will roll back to %s", content.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update DaemonSet template, and retry on conflict
|
// Skip if the revision already matches current DaemonSet
|
||||||
skipUpdate := false
|
done, err := daemon.Match(versionedDS, toHistory)
|
||||||
retryErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
if err != nil {
|
||||||
var err error
|
return "", err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
if skipUpdate {
|
if done {
|
||||||
return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
|
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) {
|
return rollbackSuccess, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func revisionNotFoundErr(r int64) error {
|
func revisionNotFoundErr(r int64) error {
|
||||||
|
|
|
@ -39,6 +39,7 @@ func versionedClientsetForDaemonSet(internalClient internalclientset.Interface)
|
||||||
return &externalclientset.Clientset{}
|
return &externalclientset.Clientset{}
|
||||||
}
|
}
|
||||||
return &externalclientset.Clientset{
|
return &externalclientset.Clientset{
|
||||||
AppsV1beta1Client: apps.New(internalClient.Apps().RESTClient()),
|
AppsV1beta1Client: apps.New(internalClient.Apps().RESTClient()),
|
||||||
|
ExtensionsV1beta1Client: extensions.New(internalClient.Extensions().RESTClient()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,7 +268,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
|
||||||
// Check history and labels
|
// Check history and labels
|
||||||
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
|
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
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]
|
firstHash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
|
||||||
Expect(first.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(firstHash))
|
Expect(first.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(firstHash))
|
||||||
Expect(first.Revision).To(Equal(int64(1)))
|
Expect(first.Revision).To(Equal(int64(1)))
|
||||||
|
@ -295,7 +295,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
|
||||||
// Check history and labels
|
// Check history and labels
|
||||||
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
|
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
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]
|
curHash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
|
||||||
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(curHash))
|
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(curHash))
|
||||||
Expect(cur.Revision).To(Equal(int64(2)))
|
Expect(cur.Revision).To(Equal(int64(2)))
|
||||||
|
@ -325,7 +325,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
|
||||||
// Check history and labels
|
// Check history and labels
|
||||||
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
|
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
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]
|
hash := ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
|
||||||
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(hash))
|
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(hash))
|
||||||
Expect(cur.Revision).To(Equal(int64(1)))
|
Expect(cur.Revision).To(Equal(int64(1)))
|
||||||
|
@ -353,7 +353,7 @@ var _ = framework.KubeDescribe("Daemon set [Serial]", func() {
|
||||||
// Check history and labels
|
// Check history and labels
|
||||||
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
|
ds, err = c.Extensions().DaemonSets(ns).Get(ds.Name, metav1.GetOptions{})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
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]
|
hash = ds.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
|
||||||
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(hash))
|
Expect(cur.Labels[extensions.DefaultDaemonSetUniqueLabelKey]).To(Equal(hash))
|
||||||
Expect(cur.Revision).To(Equal(int64(2)))
|
Expect(cur.Revision).To(Equal(int64(2)))
|
||||||
|
@ -760,14 +760,14 @@ func listDaemonHistories(c clientset.Interface, ns string, label map[string]stri
|
||||||
return historyList
|
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
|
var curHistory *apps.ControllerRevision
|
||||||
foundCurHistories := 0
|
foundCurHistories := 0
|
||||||
for i := range historyList.Items {
|
for i := range historyList.Items {
|
||||||
history := &historyList.Items[i]
|
history := &historyList.Items[i]
|
||||||
// Every history should have the hash label
|
// Every history should have the hash label
|
||||||
Expect(len(history.Labels[extensions.DefaultDaemonSetUniqueLabelKey])).To(BeNumerically(">", 0))
|
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())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
if match {
|
if match {
|
||||||
curHistory = history
|
curHistory = history
|
||||||
|
|
Loading…
Reference in New Issue