From 68983201bfd97562c301f893bd901a971275b3a4 Mon Sep 17 00:00:00 2001 From: Yinan Li Date: Thu, 3 Aug 2017 11:53:55 -0700 Subject: [PATCH] Added field CollisionCount to StatefulSetStatus --- pkg/apis/apps/types.go | 6 + pkg/apis/apps/validation/validation.go | 30 +++++ pkg/apis/apps/validation/validation_test.go | 114 +++++++++++++++++++ staging/src/k8s.io/api/apps/v1beta1/types.go | 6 + staging/src/k8s.io/api/apps/v1beta2/types.go | 6 + 5 files changed, 162 insertions(+) diff --git a/pkg/apis/apps/types.go b/pkg/apis/apps/types.go index 5e4173b0828..9841c2ab4f6 100644 --- a/pkg/apis/apps/types.go +++ b/pkg/apis/apps/types.go @@ -187,6 +187,12 @@ type StatefulSetStatus struct { // updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence // [replicas-updatedReplicas,replicas) UpdateRevision string + + // collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller + // uses this field as a collision avoidance mechanism when it needs to create the name for the + // newest ControllerRevision. + // +optional + CollisionCount *int64 } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go index d4a02bc7434..6b0e7d98534 100644 --- a/pkg/apis/apps/validation/validation.go +++ b/pkg/apis/apps/validation/validation.go @@ -163,9 +163,39 @@ func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) fi return allErrs } +// ValidateStatefulSetStatus validates a StatefulSetStatus. +func ValidateStatefulSetStatus(status *apps.StatefulSetStatus, fieldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fieldPath.Child("replicas"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fieldPath.Child("readyReplicas"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentReplicas), fieldPath.Child("currentReplicas"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fieldPath.Child("updatedReplicas"))...) + if status.ObservedGeneration != nil { + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.ObservedGeneration), fieldPath.Child("observedGeneration"))...) + } + if status.CollisionCount != nil { + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fieldPath.Child("collisionCount"))...) + } + + msg := "cannot be greater than status.replicas" + if status.ReadyReplicas > status.Replicas { + allErrs = append(allErrs, field.Invalid(fieldPath.Child("readyReplicas"), status.ReadyReplicas, msg)) + } + if status.CurrentReplicas > status.Replicas { + allErrs = append(allErrs, field.Invalid(fieldPath.Child("currentReplicas"), status.CurrentReplicas, msg)) + } + if status.UpdatedReplicas > status.Replicas { + allErrs = append(allErrs, field.Invalid(fieldPath.Child("updatedReplicas"), status.UpdatedReplicas, msg)) + } + + return allErrs +} + // ValidateStatefulSetStatusUpdate tests if required fields in the StatefulSet are set. func ValidateStatefulSetStatusUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) field.ErrorList { allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateStatefulSetStatus(&statefulSet.Status, field.NewPath("status"))...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata"))...) // TODO: Validate status. return allErrs diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go index f968fdc83cb..9071fa189f0 100644 --- a/pkg/apis/apps/validation/validation_test.go +++ b/pkg/apis/apps/validation/validation_test.go @@ -22,6 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/apps" ) @@ -300,6 +301,119 @@ func TestValidateStatefulSet(t *testing.T) { } } +func TestValidateStatefulSetStatus(t *testing.T) { + minusOne := int64(-1) + tests := []struct { + name string + replicas int32 + readyReplicas int32 + currentReplicas int32 + updatedReplicas int32 + observedGeneration *int64 + collisionCount *int64 + expectedErr bool + }{ + { + name: "valid status", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 1, + expectedErr: false, + }, + { + name: "invalid replicas", + replicas: -1, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 1, + expectedErr: true, + }, + { + name: "invalid readyReplicas", + replicas: 3, + readyReplicas: -1, + currentReplicas: 2, + updatedReplicas: 1, + expectedErr: true, + }, + { + name: "invalid currentReplicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: -1, + updatedReplicas: 1, + expectedErr: true, + }, + { + name: "invalid updatedReplicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: -1, + expectedErr: true, + }, + { + name: "invalid observedGeneration", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 1, + observedGeneration: &minusOne, + expectedErr: true, + }, + { + name: "invalid collisionCount", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 1, + collisionCount: &minusOne, + expectedErr: true, + }, + { + name: "readyReplicas greater than replicas", + replicas: 3, + readyReplicas: 4, + currentReplicas: 2, + updatedReplicas: 1, + expectedErr: true, + }, + { + name: "currentReplicas greater than replicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: 4, + updatedReplicas: 1, + expectedErr: true, + }, + { + name: "updatedReplicas greater than replicas", + replicas: 3, + readyReplicas: 3, + currentReplicas: 2, + updatedReplicas: 4, + expectedErr: true, + }, + } + + for _, test := range tests { + status := apps.StatefulSetStatus{ + Replicas: test.replicas, + ReadyReplicas: test.readyReplicas, + CurrentReplicas: test.currentReplicas, + UpdatedReplicas: test.updatedReplicas, + ObservedGeneration: test.observedGeneration, + CollisionCount: test.collisionCount, + } + + errs := ValidateStatefulSetStatus(&status, field.NewPath("status")) + if hasErr := len(errs) > 0; hasErr != test.expectedErr { + t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errs.ToAggregate().Error()) + } + } +} + func TestValidateStatefulSetUpdate(t *testing.T) { validLabels := map[string]string{"a": "b"} validPodTemplate := api.PodTemplate{ diff --git a/staging/src/k8s.io/api/apps/v1beta1/types.go b/staging/src/k8s.io/api/apps/v1beta1/types.go index dbdadaed93b..afd5b4fa8d4 100644 --- a/staging/src/k8s.io/api/apps/v1beta1/types.go +++ b/staging/src/k8s.io/api/apps/v1beta1/types.go @@ -239,6 +239,12 @@ type StatefulSetStatus struct { // updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence // [replicas-updatedReplicas,replicas) UpdateRevision string `json:"updateRevision,omitempty" protobuf:"bytes,7,opt,name=updateRevision"` + + // collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller + // uses this field as a collision avoidance mechanism when it needs to create the name for the + // newest ControllerRevision. + // +optional + CollisionCount *int64 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/staging/src/k8s.io/api/apps/v1beta2/types.go b/staging/src/k8s.io/api/apps/v1beta2/types.go index 6aada49f6e2..764d168dc96 100644 --- a/staging/src/k8s.io/api/apps/v1beta2/types.go +++ b/staging/src/k8s.io/api/apps/v1beta2/types.go @@ -246,6 +246,12 @@ type StatefulSetStatus struct { // updateRevision, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence // [replicas-updatedReplicas,replicas) UpdateRevision string `json:"updateRevision,omitempty" protobuf:"bytes,7,opt,name=updateRevision"` + + // collisionCount is the count of hash collisions for the StatefulSet. The StatefulSet controller + // uses this field as a collision avoidance mechanism when it needs to create the name for the + // newest ControllerRevision. + // +optional + CollisionCount *int64 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object