Merge pull request #86506 from dineshba/master

sts: rollout history will show the details of the sts if the revision is specfied
This commit is contained in:
Kubernetes Prow Robot 2020-09-22 16:52:08 -07:00 committed by GitHub
commit ef57a095d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 192 additions and 55 deletions

View File

@ -95,6 +95,7 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library", "//staging/src/k8s.io/client-go/testing:go_default_library",

View File

@ -183,6 +183,18 @@ func (h *DaemonSetHistoryViewer) ViewHistory(namespace, name string, revision in
if err != nil { if err != nil {
return "", err return "", err
} }
return printHistory(history, revision, func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error) {
dsOfHistory, err := applyDaemonSetHistory(ds, history)
if err != nil {
return nil, err
}
return &dsOfHistory.Spec.Template, err
})
}
// printHistory returns the podTemplate of the given revision if it is non-zero
// else returns the overall revisions
func printHistory(history []*appsv1.ControllerRevision, revision int64, getPodTemplate func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error)) (string, error) {
historyInfo := make(map[int64]*appsv1.ControllerRevision) historyInfo := make(map[int64]*appsv1.ControllerRevision)
for _, history := range history { for _, history := range history {
// TODO: for now we assume revisions don't overlap, we may need to handle it // TODO: for now we assume revisions don't overlap, we may need to handle it
@ -198,11 +210,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")
} }
dsOfHistory, err := applyDaemonSetHistory(ds, history) podTemplate, err := getPodTemplate(history)
if err != nil { if err != nil {
return "", fmt.Errorf("unable to parse history %s", history.Name) return "", fmt.Errorf("unable to parse history %s", history.Name)
} }
return printTemplate(&dsOfHistory.Spec.Template) return printTemplate(podTemplate)
} }
// Print an overview of all Revisions // Print an overview of all Revisions
@ -233,28 +245,17 @@ type StatefulSetHistoryViewer struct {
// ViewHistory returns a list of the revision history of a statefulset // ViewHistory returns a list of the revision history of a statefulset
// TODO: this should be a describer // TODO: this should be a describer
// TODO: needs to implement detailed revision view
func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) { func (h *StatefulSetHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
_, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name) sts, history, err := statefulSetHistory(h.c.AppsV1(), namespace, name)
if err != nil { if err != nil {
return "", err return "", err
} }
return printHistory(history, revision, func(history *appsv1.ControllerRevision) (*corev1.PodTemplateSpec, error) {
if len(history) <= 0 { stsOfHistory, err := applyStatefulSetHistory(sts, history)
return "No rollout history found.", nil if err != nil {
} return nil, err
revisions := make([]int64, 0, len(history))
for _, revision := range history {
revisions = append(revisions, revision.Revision)
}
sliceutil.SortInts64(revisions)
return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "REVISION\n")
for _, r := range revisions {
fmt.Fprintf(out, "%d\n", r)
} }
return nil return &stsOfHistory.Spec.Template, err
}) })
} }
@ -365,6 +366,24 @@ func applyDaemonSetHistory(ds *appsv1.DaemonSet, history *appsv1.ControllerRevis
return result, nil return result, nil
} }
// applyStatefulSetHistory returns a specific revision of StatefulSet by applying the given history to a copy of the given StatefulSet
func applyStatefulSetHistory(sts *appsv1.StatefulSet, history *appsv1.ControllerRevision) (*appsv1.StatefulSet, error) {
stsBytes, err := json.Marshal(sts)
if err != nil {
return nil, err
}
patched, err := strategicpatch.StrategicMergePatch(stsBytes, history.Data.Raw, sts)
if err != nil {
return nil, err
}
result := &appsv1.StatefulSet{}
err = json.Unmarshal(patched, result)
if err != nil {
return nil, err
}
return result, nil
}
// TODO: copied here until this becomes a describer // TODO: copied here until this becomes a describer
func tabbedString(f func(io.Writer) error) (string, error) { func tabbedString(f func(io.Writer) error) (string, error) {
out := new(tabwriter.Writer) out := new(tabwriter.Writer)

View File

@ -27,6 +27,7 @@ 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/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
) )
@ -53,60 +54,176 @@ func TestHistoryViewerFor(t *testing.T) {
func TestViewHistory(t *testing.T) { func TestViewHistory(t *testing.T) {
var ( t.Run("for statefulSet", func(t *testing.T) {
trueVar = true var (
replicas = int32(1) trueVar = true
replicas = int32(1)
podStub = corev1.PodTemplateSpec{ podStub = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}},
}
ssStub = &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "moons",
Namespace: "default",
UID: "1993",
Labels: map[string]string{"foo": "bar"},
},
Spec: appsv1.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Replicas: &replicas, Template: podStub},
}
)
stsRawData, err := json.Marshal(ssStub)
if err != nil {
t.Fatalf("error creating sts raw data: %v", err)
} }
ssStub1 := &appsv1.ControllerRevision{
ssStub = &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "moons",
Namespace: "default",
UID: "1993",
Labels: map[string]string{"foo": "bar"},
},
Spec: appsv1.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Replicas: &replicas, Template: podStub},
}
ssStub1 = &appsv1.ControllerRevision{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "moons", Name: "moons",
Namespace: "default", Namespace: "default",
Labels: map[string]string{"foo": "bar"}, Labels: map[string]string{"foo": "bar"},
OwnerReferences: []metav1.OwnerReference{{"apps/v1", "StatefulSet", "moons", "1993", &trueVar, nil}}, OwnerReferences: []metav1.OwnerReference{{"apps/v1", "StatefulSet", "moons", "1993", &trueVar, nil}},
}, },
Data: runtime.RawExtension{Raw: stsRawData},
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"}, TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"},
Revision: 1, Revision: 1,
} }
)
fakeClientSet := fake.NewSimpleClientset(ssStub) fakeClientSet := fake.NewSimpleClientset(ssStub)
_, err := fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), ssStub1, metav1.CreateOptions{}) _, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), ssStub1, metav1.CreateOptions{})
if err != nil { if err != nil {
t.Fatalf("create controllerRevisions error %v occurred ", err) t.Fatalf("create controllerRevisions error %v occurred ", err)
} }
var sts = &StatefulSetHistoryViewer{ var sts = &StatefulSetHistoryViewer{
fakeClientSet, fakeClientSet,
} }
result, err := sts.ViewHistory("default", "moons", 1) t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
if err != nil { result, err := sts.ViewHistory("default", "moons", 0)
t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err) if err != nil {
} t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
}
expected := `REVISION expected := `REVISION CHANGE-CAUSE
1 1 <none>
` `
if result != expected { if result != expected {
t.Fatalf("unexpected output (%v was expected but got %v)", expected, result) t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
} }
})
t.Run("should describe the revision if revision is specified", func(t *testing.T) {
result, err := sts.ViewHistory("default", "moons", 1)
if err != nil {
t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
}
expected := `Pod Template:
Labels: foo=bar
Containers:
test:
Image: nginx
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
`
if result != expected {
t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
}
})
})
t.Run("for daemonSet", func(t *testing.T) {
var (
trueVar = true
podStub = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}},
}
daemonSetStub = &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{
Name: "moons",
Namespace: "default",
UID: "1993",
Labels: map[string]string{"foo": "bar"},
},
Spec: appsv1.DaemonSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Template: podStub},
}
)
daemonSetRaw, err := json.Marshal(daemonSetStub)
if err != nil {
t.Fatalf("error creating sts raw data: %v", err)
}
daemonSetControllerRevision := &appsv1.ControllerRevision{
ObjectMeta: metav1.ObjectMeta{
Name: "moons",
Namespace: "default",
Labels: map[string]string{"foo": "bar"},
OwnerReferences: []metav1.OwnerReference{{"apps/v1", "DaemonSet", "moons", "1993", &trueVar, nil}},
},
Data: runtime.RawExtension{Raw: daemonSetRaw},
TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"},
Revision: 1,
}
fakeClientSet := fake.NewSimpleClientset(daemonSetStub)
_, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), daemonSetControllerRevision, metav1.CreateOptions{})
if err != nil {
t.Fatalf("create controllerRevisions error %v occurred ", err)
}
var daemonSetHistoryViewer = &DaemonSetHistoryViewer{
fakeClientSet,
}
t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 0)
if err != nil {
t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
}
expected := `REVISION CHANGE-CAUSE
1 <none>
`
if result != expected {
t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
}
})
t.Run("should describe the revision if revision is specified", func(t *testing.T) {
result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 1)
if err != nil {
t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
}
expected := `Pod Template:
Labels: foo=bar
Containers:
test:
Image: nginx
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
`
if result != expected {
t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
}
})
})
} }
func TestApplyDaemonSetHistory(t *testing.T) { func TestApplyDaemonSetHistory(t *testing.T) {