kruise/pkg/controller/cloneset/scale/cloneset_scale_test.go

458 lines
13 KiB
Go

package scale
import (
"context"
"reflect"
"sort"
"testing"
appsv1alpha1 "github.com/openkruise/kruise/apis/apps/v1alpha1"
clonesettest "github.com/openkruise/kruise/pkg/controller/cloneset/test"
"github.com/openkruise/kruise/pkg/util"
"github.com/openkruise/kruise/pkg/util/expectations"
apps "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/record"
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func newFakeControl() *realControl {
return &realControl{
Client: fake.NewFakeClient(),
recorder: record.NewFakeRecorder(10),
exp: expectations.NewScaleExpectations(),
}
}
func TestCreatePods(t *testing.T) {
currentCS := clonesettest.NewCloneSet(3)
updateCS := currentCS.DeepCopy()
updateCS.Spec.Template.Spec.Containers[0].Env = []v1.EnvVar{{Name: "e-key", Value: "e-value"}}
currentRevision := "revision-abc"
updateRevision := "revision-xyz"
ctrl := newFakeControl()
created, err := ctrl.createPods(
3,
1,
currentCS,
updateCS,
currentRevision,
updateRevision,
[]string{"id1", "id3", "id4"},
sets.NewString("datadir-foo-id3"),
)
if err != nil {
t.Fatalf("got unexpected error: %v", err)
} else if !created {
t.Fatalf("got unexpected created: %v", created)
}
pods := v1.PodList{}
if err := ctrl.List(context.TODO(), &pods, client.InNamespace("default")); err != nil {
t.Fatalf("failed to list pods: %v", err)
}
expectedPods := []v1.Pod{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-id1",
GenerateName: "foo-",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id1",
apps.ControllerRevisionHashLabelKey: "revision-abc",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
ResourceVersion: "1",
},
Spec: v1.PodSpec{
ReadinessGates: []v1.PodReadinessGate{{ConditionType: appsv1alpha1.InPlaceUpdateReady}},
Containers: []v1.Container{
{
Name: "nginx",
Image: "nginx",
VolumeMounts: []v1.VolumeMount{
{Name: "datadir", MountPath: "/tmp/data"},
{Name: "home", MountPath: "/home"},
},
},
},
Volumes: []v1.Volume{
{
Name: "datadir",
VolumeSource: v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: "datadir-foo-id1",
ReadOnly: false,
},
},
},
{
Name: "home",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: "/tmp/home",
},
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-id3",
GenerateName: "foo-",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id3",
apps.ControllerRevisionHashLabelKey: "revision-xyz",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
ResourceVersion: "1",
},
Spec: v1.PodSpec{
ReadinessGates: []v1.PodReadinessGate{{ConditionType: appsv1alpha1.InPlaceUpdateReady}},
Containers: []v1.Container{
{
Name: "nginx",
Image: "nginx",
Env: []v1.EnvVar{{Name: "e-key", Value: "e-value"}},
VolumeMounts: []v1.VolumeMount{
{Name: "datadir", MountPath: "/tmp/data"},
{Name: "home", MountPath: "/home"},
},
},
},
Volumes: []v1.Volume{
{
Name: "datadir",
VolumeSource: v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: "datadir-foo-id3",
ReadOnly: false,
},
},
},
{
Name: "home",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: "/tmp/home",
},
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-id4",
GenerateName: "foo-",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id4",
apps.ControllerRevisionHashLabelKey: "revision-xyz",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
ResourceVersion: "1",
},
Spec: v1.PodSpec{
ReadinessGates: []v1.PodReadinessGate{{ConditionType: appsv1alpha1.InPlaceUpdateReady}},
Containers: []v1.Container{
{
Name: "nginx",
Image: "nginx",
Env: []v1.EnvVar{{Name: "e-key", Value: "e-value"}},
VolumeMounts: []v1.VolumeMount{
{Name: "datadir", MountPath: "/tmp/data"},
{Name: "home", MountPath: "/home"},
},
},
},
Volumes: []v1.Volume{
{
Name: "datadir",
VolumeSource: v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: "datadir-foo-id4",
ReadOnly: false,
},
},
},
{
Name: "home",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: "/tmp/home",
},
},
},
},
},
},
}
for i := range expectedPods {
appsv1alpha1.SetDefaultPod(&expectedPods[i])
}
sort.Slice(pods.Items, func(i, j int) bool { return pods.Items[i].Name < pods.Items[j].Name })
if !reflect.DeepEqual(expectedPods, pods.Items) {
t.Fatalf("expected pods \n%s\ngot pods\n%s", util.DumpJSON(expectedPods), util.DumpJSON(pods.Items))
}
pvcs := v1.PersistentVolumeClaimList{}
if err := ctrl.List(context.TODO(), &pvcs, client.InNamespace("default")); err != nil {
t.Fatalf("failed to list pvcs: %v", err)
}
expectedPVCs := []v1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "datadir-foo-id1",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id1",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
ResourceVersion: "1",
},
Spec: v1.PersistentVolumeClaimSpec{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("10"),
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "datadir-foo-id4",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id4",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
ResourceVersion: "1",
},
Spec: v1.PersistentVolumeClaimSpec{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("10"),
},
},
},
},
}
for i := range expectedPVCs {
corev1.SetDefaults_PersistentVolumeClaim(&expectedPVCs[i])
}
sort.Slice(pvcs.Items, func(i, j int) bool { return pvcs.Items[i].Name < pvcs.Items[j].Name })
if !reflect.DeepEqual(expectedPVCs, pvcs.Items) {
t.Fatalf("expected pvcs \n%s\ngot pvcs\n%s", util.DumpJSON(expectedPVCs), util.DumpJSON(pvcs.Items))
}
exp := ctrl.exp.GetExpectations("default/foo")
expectedExp := map[expectations.ScaleAction]sets.String{
expectations.Create: sets.NewString("foo-id1", "foo-id3", "foo-id4", "datadir-foo-id1", "datadir-foo-id4"),
}
if !reflect.DeepEqual(expectedExp, exp) {
t.Fatalf("expected expectations: %v, got %v", expectedExp, exp)
}
}
func TestDeletePods(t *testing.T) {
cs := &appsv1alpha1.CloneSet{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "foo"}}
podsToDelete := []*v1.Pod{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-id1",
GenerateName: "foo-",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id1",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-id3",
GenerateName: "foo-",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id3",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
},
},
}
pvcs := []*v1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "datadir-foo-id1",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id1",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "datadir-foo-id2",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id2",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "datadir-foo-id3",
Labels: map[string]string{
appsv1alpha1.CloneSetInstanceID: "id3",
"foo": "bar",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps.kruise.io/v1alpha1",
Kind: "CloneSet",
Name: "foo",
UID: "test",
Controller: func() *bool { a := true; return &a }(),
BlockOwnerDeletion: func() *bool { a := true; return &a }(),
},
},
},
},
}
ctrl := newFakeControl()
for _, p := range podsToDelete {
_ = ctrl.Create(context.TODO(), p)
}
for _, p := range pvcs {
_ = ctrl.Create(context.TODO(), p)
}
deleted, err := ctrl.deletePods(cs, podsToDelete, pvcs)
if err != nil {
t.Fatalf("failed to delete got pods: %v", err)
} else if !deleted {
t.Fatalf("failed to delete got pods: not deleted")
}
gotPods := v1.PodList{}
if err := ctrl.List(context.TODO(), &gotPods, client.InNamespace("default")); err != nil {
t.Fatalf("failed to list pods: %v", err)
}
if len(gotPods.Items) > 0 {
t.Fatalf("expected no pods left, actually: %v", gotPods.Items)
}
gotPVCs := v1.PersistentVolumeClaimList{}
if err := ctrl.List(context.TODO(), &gotPVCs, client.InNamespace("default")); err != nil {
t.Fatalf("failed to list pvcs: %v", err)
}
if len(gotPVCs.Items) != 1 || reflect.DeepEqual(gotPVCs.Items[0], pvcs[1]) {
t.Fatalf("unexpected pvcs: %v", util.DumpJSON(gotPVCs.Items))
}
}