Change:
StatefulSet now as of 1.6. That means the TODO on can be fixed in 1.7. We could not fix it in 1.6 because that code is used by kubectl, which must work against servers that are one minor version ahead of or behind the client.
/cc
I am volunteered to take this job if no one start working on this.
I don't think anyone's working on it, so you are welcome to take it. Feel free to tag me as a reviewer. Thanks!

Discussion:
} } <del> // StatefulSetHasDesiredReplicas returns a conditon that checks the number of statefulset replicas </del> <ins> // StatefulSetHasDesiredReplicas returns a condition that checks the number of StatefulSet replicas </ins> func StatefulSetHasDesiredReplicas(ssClient appsclient.StatefulSetsGetter, ss *apps.StatefulSet) wait.ConditionFunc { <del> // TODO: Differentiate between 0 statefulset pods and a really quick scale down using generation. </del> <ins> // If we're given a StatefulSet where the status lags the spec, it either means that the // StatefulSet is stale, or that the StatefulSet manager hasn't noticed the update yet. // Polling status.Replicas is not safe in the latter case. desiredGeneration := ss.Generation </ins> return func() (bool, error) { ss, err := ssClient.StatefulSets(ss.Namespace).Get(ss.Name, metav1.GetOptions{}) if err != nil { return false, err } <del> return ss.Status.Replicas == ss.Spec.Replicas, nil </del> <ins> // There's a chance a concurrent update modifies the Spec.Replicas causing this check to // pass, or, after this check has passed, a modification causes the StatefulSet manager to // create more pods. This will not be an issue once we've implemented graceful delete for // StatefulSet, but till then concurrent stop operations on the same StatefulSet might have // unintended side effects. return ss.Status.ObservedGeneration != nil && *ss.Status.ObservedGeneration >= desiredGeneration && ss.Status.Replicas == ss.Spec.Replicas, nil </ins> } }

<ins> package(default_visibility = ["//visibility:public"]) </ins> licenses(["notice"]) load(

"go_test", ) <ins> go_test( name = "go_default_test", srcs = [ "cluster_test.go", "configmap_test.go", "deployment_test.go", "generate_test.go", "kubectl_test.go", "namespace_test.go", "proxy_server_test.go", "quota_test.go", "rolebinding_test.go", "rolling_updater_test.go", "rollout_status_test.go", "run_test.go", "scale_test.go", "secret_for_docker_registry_test.go", "secret_for_tls_test.go", "secret_test.go", "service_basic_test.go", "service_test.go", "serviceaccount_test.go", "sorting_printer_test.go", "stop_test.go", ], library = ":go_default_library", tags = ["automanaged"], deps = [ "//federation/apis/federation/v1beta1:go_default_library", "//pkg/api:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", "//pkg/api/v1:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/apps/v1beta1:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/batch/v1:go_default_library", "//pkg/apis/batch/v2alpha1:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library", "//pkg/kubectl/util:go_default_library", "//vendor/github.com/spf13/cobra: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/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", ], ) </ins> go_library( name = "go_default_library", srcs = [

"versioned_client.go", ], tags = ["automanaged"], <del> visibility = [ "//build/visible_to:COMMON_testing", "//build/visible_to:pkg_kubectl_CONSUMERS", ], </del> deps = [ "//federation/apis/federation/v1beta1:go_default_library", "//pkg/api:go_default_library",

], ) <del> go_test( name = "go_default_test", srcs = [ "cluster_test.go", "configmap_test.go", "deployment_test.go", "generate_test.go", "kubectl_test.go", "namespace_test.go", "proxy_server_test.go", "quota_test.go", "rolebinding_test.go", "rolling_updater_test.go", "rollout_status_test.go", "run_test.go", "scale_test.go", "secret_for_docker_registry_test.go", "secret_for_tls_test.go", "secret_test.go", "service_basic_test.go", "service_test.go", "serviceaccount_test.go", "sorting_printer_test.go", "stop_test.go", ], library = ":go_default_library", tags = ["automanaged"], deps = [ "//federation/apis/federation/v1beta1:go_default_library", "//pkg/api:go_default_library", "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", "//pkg/api/v1:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/apps/v1beta1:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/batch/v1:go_default_library", "//pkg/apis/batch/v2alpha1:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/apis/rbac:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library", "//pkg/kubectl/util:go_default_library", "//vendor/github.com/spf13/cobra: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/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library", ], ) </del> filegroup( name = "package-srcs", srcs = glob(["**"]), tags = ["automanaged"], <ins> visibility = ["//visibility:private"], </ins> ) filegroup(

"//pkg/kubectl/util:all-srcs", ], tags = ["automanaged"], <del> visibility = [ "//build/visible_to:pkg_kubectl_CONSUMERS", ], </del> )

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" <del> "k8s.io/kubernetes/pkg/controller" </del> deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" "k8s.io/kubernetes/pkg/util" )

return err } <del> // TODO: This shouldn't be needed, see corresponding TODO in StatefulSetHasDesiredReplicas. // StatefulSet should track generation number. pods := reaper.podClient.Pods(namespace) selector, _ := metav1.LabelSelectorAsSelector(ss.Spec.Selector) options := metav1.ListOptions{LabelSelector: selector.String()} podList, err := pods.List(options) if err != nil { return err } errList := []error{} for i := range podList.Items { pod := &podList.Items[i] controllerRef := controller.GetControllerOf(pod) // Ignore Pod if it's an orphan or owned by someone else. if controllerRef == nil || controllerRef.UID != ss.UID { continue } if err := pods.Delete(pod.Name, gracePeriod); err != nil { if !errors.IsNotFound(err) { errList = append(errList, err) } } } if len(errList) > 0 { return utilerrors.NewAggregate(errList) } </del> // TODO: Cleanup volumes? We don't want to accidentally delete volumes from // stop, so just leave this up to the statefulset. falseVar := false