diff --git a/pkg/kubectl/scale_test.go b/pkg/kubectl/scale_test.go index 85c77a1ffb..96250d8fe1 100644 --- a/pkg/kubectl/scale_test.go +++ b/pkg/kubectl/scale_test.go @@ -23,10 +23,12 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" testcore "k8s.io/client-go/testing" + "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + appsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/internalversion" batchclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/internalversion" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" @@ -785,3 +787,265 @@ func TestValidateDeployment(t *testing.T) { } } } + +type ErrorStatefulSets struct { + appsclient.StatefulSetInterface + conflict bool + invalid bool +} + +func (c *ErrorStatefulSets) Update(statefulSet *apps.StatefulSet) (*apps.StatefulSet, error) { + switch { + case c.invalid: + return nil, kerrors.NewInvalid(api.Kind(statefulSet.Kind), statefulSet.Name, nil) + case c.conflict: + return nil, kerrors.NewConflict(api.Resource(statefulSet.Kind), statefulSet.Name, nil) + } + return nil, errors.New("statefulSet update failure") +} + +func (c *ErrorStatefulSets) Get(name string, options metav1.GetOptions) (*apps.StatefulSet, error) { + return &apps.StatefulSet{ + Spec: apps.StatefulSetSpec{ + Replicas: 0, + }, + }, nil +} + +type ErrorStatefulSetClient struct { + appsclient.StatefulSetsGetter + conflict bool + invalid bool +} + +func (c *ErrorStatefulSetClient) StatefulSets(namespace string) appsclient.StatefulSetInterface { + return &ErrorStatefulSets{ + StatefulSetInterface: c.StatefulSetsGetter.StatefulSets(namespace), + invalid: c.invalid, + conflict: c.conflict, + } +} + +func statefulSet() *apps.StatefulSet { + return &apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + Name: "foo", + }, + } +} + +func TestStatefulSetScale(t *testing.T) { + fake := fake.NewSimpleClientset(statefulSet()) + scaler := StatefulSetScaler{fake.Apps()} + preconditions := ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + scaler.Scale("default", name, count, &preconditions, nil, nil) + + actions := fake.Actions() + if len(actions) != 2 { + t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", actions) + } + + if action, ok := actions[0].(testcore.GetAction); !ok || action.GetResource().GroupResource() != apps.Resource("statefulsets") || action.GetName() != name { + t.Errorf("unexpected action: %v, expected get-statefulsets %s", actions[0], name) + } + if action, ok := actions[1].(testcore.UpdateAction); !ok || action.GetResource().GroupResource() != apps.Resource("statefulsets") || action.GetObject().(*apps.StatefulSet).Spec.Replicas != int32(count) { + t.Errorf("unexpected action %v, expected update-statefulset with replicas = %d", actions[1], count) + } +} + +func TestStatefulSetScaleRetry(t *testing.T) { + fake := &ErrorStatefulSetClient{StatefulSetsGetter: fake.NewSimpleClientset().Apps(), conflict: true} + scaler := &StatefulSetScaler{fake} + preconditions := &ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + namespace := "default" + + scaleFunc := ScaleCondition(scaler, preconditions, namespace, name, count, nil) + pass, err := scaleFunc() + if pass != false { + t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass) + } + if err != nil { + t.Errorf("Did not expect an error on update failure, got %v", err) + } + preconditions = &ScalePrecondition{3, ""} + scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil) + pass, err = scaleFunc() + if err == nil { + t.Error("Expected error on precondition failure") + } +} + +func TestStatefulSetScaleInvalid(t *testing.T) { + fake := &ErrorStatefulSetClient{StatefulSetsGetter: fake.NewSimpleClientset().Apps(), invalid: true} + scaler := StatefulSetScaler{fake} + preconditions := ScalePrecondition{-1, ""} + count := uint(3) + name := "foo" + namespace := "default" + + scaleFunc := ScaleCondition(&scaler, &preconditions, namespace, name, count, nil) + pass, err := scaleFunc() + if pass { + t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass) + } + e, ok := err.(ScaleError) + if err == nil || !ok || e.FailureType != ScaleUpdateFailure { + t.Errorf("Expected error on invalid update failure, got %v", err) + } +} + +func TestStatefulSetScaleFailsPreconditions(t *testing.T) { + fake := fake.NewSimpleClientset(&apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + Name: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: 10, + }, + }) + scaler := StatefulSetScaler{fake.Apps()} + preconditions := ScalePrecondition{2, ""} + count := uint(3) + name := "foo" + scaler.Scale("default", name, count, &preconditions, nil, nil) + + actions := fake.Actions() + if len(actions) != 1 { + t.Errorf("unexpected actions: %v, expected 1 actions (get)", actions) + } + if action, ok := actions[0].(testcore.GetAction); !ok || action.GetResource().GroupResource() != apps.Resource("statefulsets") || action.GetName() != name { + t.Errorf("unexpected action: %v, expected get-statefulset %s", actions[0], name) + } +} + +func TestValidateStatefulSet(t *testing.T) { + zero, ten, twenty := int32(0), int32(10), int32(20) + tests := []struct { + preconditions ScalePrecondition + statefulset apps.StatefulSet + expectError bool + test string + }{ + { + preconditions: ScalePrecondition{-1, ""}, + expectError: false, + test: "defaults", + }, + { + preconditions: ScalePrecondition{-1, ""}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "defaults 2", + }, + { + preconditions: ScalePrecondition{0, ""}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: zero, + }, + }, + expectError: false, + test: "size matches", + }, + { + preconditions: ScalePrecondition{-1, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "resource version matches", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: ten, + }, + }, + expectError: false, + test: "both match", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + Spec: apps.StatefulSetSpec{ + Replicas: twenty, + }, + }, + expectError: true, + test: "size different", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "foo", + }, + }, + expectError: true, + test: "no replicas", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "bar", + }, + Spec: apps.StatefulSetSpec{ + Replicas: ten, + }, + }, + expectError: true, + test: "version different", + }, + { + preconditions: ScalePrecondition{10, "foo"}, + statefulset: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "bar", + }, + Spec: apps.StatefulSetSpec{ + Replicas: twenty, + }, + }, + expectError: true, + test: "both different", + }, + } + for _, test := range tests { + err := test.preconditions.ValidateStatefulSet(&test.statefulset) + if err != nil && !test.expectError { + t.Errorf("unexpected error: %v (%s)", err, test.test) + } + if err == nil && test.expectError { + t.Errorf("expected an error: %v (%s)", err, test.test) + } + } +}