Rename PetSet API to StatefulSet

This commit is contained in:
Janet Kuo 2016-10-26 13:44:07 -07:00
parent a266f72b34
commit 10aee82ae3
57 changed files with 1675 additions and 739 deletions

View File

@ -1,5 +1,5 @@
apiVersion: "apps/v1alpha1" apiVersion: "apps/v1alpha1"
kind: PetSet kind: StatefulSet
metadata: metadata:
name: calico-etcd name: calico-etcd
namespace: kube-system namespace: kube-system

View File

@ -440,15 +440,15 @@ func StartControllers(s *options.CMServer, kubeconfig *restclient.Config, rootCl
groupVersion = "apps/v1alpha1" groupVersion = "apps/v1alpha1"
resources, found = resourceMap[groupVersion] resources, found = resourceMap[groupVersion]
glog.Infof("Attempting to start petset, full resource map %+v", resourceMap) glog.Infof("Attempting to start statefulset, full resource map %+v", resourceMap)
if containsVersion(versions, groupVersion) && found { if containsVersion(versions, groupVersion) && found {
glog.Infof("Starting %s apis", groupVersion) glog.Infof("Starting %s apis", groupVersion)
if containsResource(resources, "petsets") { if containsResource(resources, "statefulsets") {
glog.Infof("Starting PetSet controller") glog.Infof("Starting StatefulSet controller")
resyncPeriod := ResyncPeriod(s)() resyncPeriod := ResyncPeriod(s)()
go petset.NewPetSetController( go petset.NewStatefulSetController(
sharedInformers.Pods().Informer(), sharedInformers.Pods().Informer(),
client("petset-controller"), client("statefulset-controller"),
resyncPeriod, resyncPeriod,
).Run(1, wait.NeverStop) ).Run(1, wait.NeverStop)
time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter))

View File

@ -130,11 +130,11 @@ func validateObject(obj runtime.Object) (errors field.ErrorList) {
t.Namespace = api.NamespaceDefault t.Namespace = api.NamespaceDefault
} }
errors = expvalidation.ValidateDaemonSet(t) errors = expvalidation.ValidateDaemonSet(t)
case *apps.PetSet: case *apps.StatefulSet:
if t.Namespace == "" { if t.Namespace == "" {
t.Namespace = api.NamespaceDefault t.Namespace = api.NamespaceDefault
} }
errors = appsvalidation.ValidatePetSet(t) errors = appsvalidation.ValidateStatefulSet(t)
default: default:
errors = field.ErrorList{} errors = field.ErrorList{}
errors = append(errors, field.InternalError(field.NewPath(""), fmt.Errorf("no validation defined for %#v", obj))) errors = append(errors, field.InternalError(field.NewPath(""), fmt.Errorf("no validation defined for %#v", obj)))
@ -221,7 +221,7 @@ func TestExampleObjectSchemas(t *testing.T) {
"cassandra-daemonset": &extensions.DaemonSet{}, "cassandra-daemonset": &extensions.DaemonSet{},
"cassandra-controller": &api.ReplicationController{}, "cassandra-controller": &api.ReplicationController{},
"cassandra-service": &api.Service{}, "cassandra-service": &api.Service{},
"cassandra-petset": &apps.PetSet{}, "cassandra-petset": &apps.StatefulSet{},
}, },
"../examples/cluster-dns": { "../examples/cluster-dns": {
"dns-backend-rc": &api.ReplicationController{}, "dns-backend-rc": &api.ReplicationController{},

View File

@ -1,5 +1,5 @@
apiVersion: "apps/v1alpha1" apiVersion: "apps/v1alpha1"
kind: PetSet kind: StatefulSet
metadata: metadata:
name: cassandra name: cassandra
spec: spec:

View File

@ -334,7 +334,7 @@ runTests() {
hpa_min_field=".spec.minReplicas" hpa_min_field=".spec.minReplicas"
hpa_max_field=".spec.maxReplicas" hpa_max_field=".spec.maxReplicas"
hpa_cpu_field=".spec.targetCPUUtilizationPercentage" hpa_cpu_field=".spec.targetCPUUtilizationPercentage"
petset_replicas_field=".spec.replicas" statefulset_replicas_field=".spec.replicas"
job_parallelism_field=".spec.parallelism" job_parallelism_field=".spec.parallelism"
deployment_replicas=".spec.replicas" deployment_replicas=".spec.replicas"
secret_data=".data" secret_data=".data"
@ -1185,7 +1185,7 @@ __EOF__
kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/pods 200 OK" kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/pods 200 OK"
kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/replicationcontrollers 200 OK" kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/replicationcontrollers 200 OK"
kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/services 200 OK" kube::test::if_has_string "${output_message}" "/api/v1/namespaces/default/services 200 OK"
kube::test::if_has_string "${output_message}" "/apis/apps/v1alpha1/namespaces/default/petsets 200 OK" kube::test::if_has_string "${output_message}" "/apis/apps/v1alpha1/namespaces/default/statefulsets 200 OK"
kube::test::if_has_string "${output_message}" "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers 200" kube::test::if_has_string "${output_message}" "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers 200"
kube::test::if_has_string "${output_message}" "/apis/batch/v1/namespaces/default/jobs 200 OK" kube::test::if_has_string "${output_message}" "/apis/batch/v1/namespaces/default/jobs 200 OK"
kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/deployments 200 OK" kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/deployments 200 OK"
@ -2396,25 +2396,25 @@ __EOF__
############ #################
# Pet Sets # # Stateful Sets #
############ #################
kube::log::status "Testing kubectl(${version}:petsets)" kube::log::status "Testing kubectl(${version}:statefulsets)"
### Create and stop petset, make sure it doesn't leak pods ### Create and stop statefulset, make sure it doesn't leak pods
# Pre-condition: no petset exists # Pre-condition: no statefulset exists
kube::test::get_object_assert petset "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert statefulset "{{range.items}}{{$id_field}}:{{end}}" ''
# Command: create petset # Command: create statefulset
kubectl create -f hack/testdata/nginx-petset.yaml "${kube_flags[@]}" kubectl create -f hack/testdata/nginx-petset.yaml "${kube_flags[@]}"
### Scale petset test with current-replicas and replicas ### Scale statefulset test with current-replicas and replicas
# Pre-condition: 0 replicas # Pre-condition: 0 replicas
kube::test::get_object_assert 'petset nginx' "{{$petset_replicas_field}}" '0' kube::test::get_object_assert 'statefulset nginx' "{{$statefulset_replicas_field}}" '0'
# Command: Scale up # Command: Scale up
kubectl scale --current-replicas=0 --replicas=1 petset nginx "${kube_flags[@]}" kubectl scale --current-replicas=0 --replicas=1 statefulset nginx "${kube_flags[@]}"
# Post-condition: 1 replica, named nginx-0 # Post-condition: 1 replica, named nginx-0
kube::test::get_object_assert 'petset nginx' "{{$petset_replicas_field}}" '1' kube::test::get_object_assert 'statefulset nginx' "{{$statefulset_replicas_field}}" '1'
# Typically we'd wait and confirm that N>1 replicas are up, but this framework # Typically we'd wait and confirm that N>1 replicas are up, but this framework
# doesn't start the scheduler, so pet-0 will block all others. # doesn't start the scheduler, so pet-0 will block all others.
# TODO: test robust scaling in an e2e. # TODO: test robust scaling in an e2e.
@ -2422,7 +2422,7 @@ __EOF__
### Clean up ### Clean up
kubectl delete -f hack/testdata/nginx-petset.yaml "${kube_flags[@]}" kubectl delete -f hack/testdata/nginx-petset.yaml "${kube_flags[@]}"
# Post-condition: no pods from petset controller # Post-condition: no pods from statefulset controller
wait-for-pods-with-label "app=nginx-petset" "" wait-for-pods-with-label "app=nginx-petset" ""

View File

@ -17,7 +17,7 @@ spec:
app: nginx-petset app: nginx-petset
--- ---
apiVersion: apps/v1alpha1 apiVersion: apps/v1alpha1
kind: PetSet kind: StatefulSet
metadata: metadata:
name: nginx name: nginx
spec: spec:

View File

@ -77,8 +77,8 @@ func TestDefaulting(t *testing.T) {
{Group: "", Version: "v1", Kind: "SecretList"}: {}, {Group: "", Version: "v1", Kind: "SecretList"}: {},
{Group: "", Version: "v1", Kind: "Service"}: {}, {Group: "", Version: "v1", Kind: "Service"}: {},
{Group: "", Version: "v1", Kind: "ServiceList"}: {}, {Group: "", Version: "v1", Kind: "ServiceList"}: {},
{Group: "apps", Version: "v1alpha1", Kind: "PetSet"}: {}, {Group: "apps", Version: "v1alpha1", Kind: "StatefulSet"}: {},
{Group: "apps", Version: "v1alpha1", Kind: "PetSetList"}: {}, {Group: "apps", Version: "v1alpha1", Kind: "StatefulSetList"}: {},
{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"}: {}, {Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"}: {},
{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscalerList"}: {}, {Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscalerList"}: {},
{Group: "batch", Version: "v1", Kind: "Job"}: {}, {Group: "batch", Version: "v1", Kind: "Job"}: {},

View File

@ -175,7 +175,7 @@ func TestKindForGroupVersionKinds(t *testing.T) {
ok: true, ok: true,
}, },
{ {
input: []GroupVersionKind{{Group: "apps", Version: "v1alpha1", Kind: "PetSet"}}, input: []GroupVersionKind{{Group: "apps", Version: "v1alpha1", Kind: "StatefulSet"}},
target: GroupVersionKind{}, target: GroupVersionKind{},
ok: false, ok: false,
}, },

View File

@ -47,8 +47,8 @@ func Resource(resource string) unversioned.GroupResource {
func addKnownTypes(scheme *runtime.Scheme) error { func addKnownTypes(scheme *runtime.Scheme) error {
// TODO this will get cleaned up with the scheme types are fixed // TODO this will get cleaned up with the scheme types are fixed
scheme.AddKnownTypes(SchemeGroupVersion, scheme.AddKnownTypes(SchemeGroupVersion,
&PetSet{}, &StatefulSet{},
&PetSetList{}, &StatefulSetList{},
&api.ListOptions{}, &api.ListOptions{},
&api.DeleteOptions{}, &api.DeleteOptions{},
) )

View File

@ -23,30 +23,30 @@ import (
// +genclient=true // +genclient=true
// PetSet represents a set of pods with consistent identities. // StatefulSet represents a set of pods with consistent identities.
// Identities are defined as: // Identities are defined as:
// - Network: A single stable DNS and hostname. // - Network: A single stable DNS and hostname.
// - Storage: As many VolumeClaims as requested. // - Storage: As many VolumeClaims as requested.
// The PetSet guarantees that a given network identity will always // The StatefulSet guarantees that a given network identity will always
// map to the same storage identity. PetSet is currently in alpha and // map to the same storage identity. StatefulSet is currently in alpha and
// and subject to change without notice. // and subject to change without notice.
type PetSet struct { type StatefulSet struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`
// +optional // +optional
api.ObjectMeta `json:"metadata,omitempty"` api.ObjectMeta `json:"metadata,omitempty"`
// Spec defines the desired identities of pets in this set. // Spec defines the desired identities of pods in this set.
// +optional // +optional
Spec PetSetSpec `json:"spec,omitempty"` Spec StatefulSetSpec `json:"spec,omitempty"`
// Status is the current status of Pets in this PetSet. This data // Status is the current status of Pods in this StatefulSet. This data
// may be out of date by some window of time. // may be out of date by some window of time.
// +optional // +optional
Status PetSetStatus `json:"status,omitempty"` Status StatefulSetStatus `json:"status,omitempty"`
} }
// A PetSetSpec is the specification of a PetSet. // A StatefulSetSpec is the specification of a StatefulSet.
type PetSetSpec struct { type StatefulSetSpec struct {
// Replicas is the desired number of replicas of the given Template. // Replicas is the desired number of replicas of the given Template.
// These are replicas in the sense that they are instantiations of the // These are replicas in the sense that they are instantiations of the
// same Template, but individual replicas also have a consistent identity. // same Template, but individual replicas also have a consistent identity.
@ -62,14 +62,14 @@ type PetSetSpec struct {
Selector *unversioned.LabelSelector `json:"selector,omitempty"` Selector *unversioned.LabelSelector `json:"selector,omitempty"`
// Template is the object that describes the pod that will be created if // Template is the object that describes the pod that will be created if
// insufficient replicas are detected. Each pod stamped out by the PetSet // insufficient replicas are detected. Each pod stamped out by the StatefulSet
// will fulfill this Template, but have a unique identity from the rest // will fulfill this Template, but have a unique identity from the rest
// of the PetSet. // of the StatefulSet.
Template api.PodTemplateSpec `json:"template"` Template api.PodTemplateSpec `json:"template"`
// VolumeClaimTemplates is a list of claims that pets are allowed to reference. // VolumeClaimTemplates is a list of claims that pods are allowed to reference.
// The PetSet controller is responsible for mapping network identities to // The StatefulSet controller is responsible for mapping network identities to
// claims in a way that maintains the identity of a pet. Every claim in // claims in a way that maintains the identity of a pod. Every claim in
// this list must have at least one matching (by name) volumeMount in one // this list must have at least one matching (by name) volumeMount in one
// container in the template. A claim in this list takes precedence over // container in the template. A claim in this list takes precedence over
// any volumes in the template, with the same name. // any volumes in the template, with the same name.
@ -77,16 +77,16 @@ type PetSetSpec struct {
// +optional // +optional
VolumeClaimTemplates []api.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"` VolumeClaimTemplates []api.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"`
// ServiceName is the name of the service that governs this PetSet. // ServiceName is the name of the service that governs this StatefulSet.
// This service must exist before the PetSet, and is responsible for // This service must exist before the StatefulSet, and is responsible for
// the network identity of the set. Pets get DNS/hostnames that follow the // the network identity of the set. Pods get DNS/hostnames that follow the
// pattern: pet-specific-string.serviceName.default.svc.cluster.local // pattern: pod-specific-string.serviceName.default.svc.cluster.local
// where "pet-specific-string" is managed by the PetSet controller. // where "pod-specific-string" is managed by the StatefulSet controller.
ServiceName string `json:"serviceName"` ServiceName string `json:"serviceName"`
} }
// PetSetStatus represents the current state of a PetSet. // StatefulSetStatus represents the current state of a StatefulSet.
type PetSetStatus struct { type StatefulSetStatus struct {
// most recent generation observed by this autoscaler. // most recent generation observed by this autoscaler.
// +optional // +optional
ObservedGeneration *int64 `json:"observedGeneration,omitempty"` ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
@ -95,10 +95,10 @@ type PetSetStatus struct {
Replicas int32 `json:"replicas"` Replicas int32 `json:"replicas"`
} }
// PetSetList is a collection of PetSets. // StatefulSetList is a collection of StatefulSets.
type PetSetList struct { type StatefulSetList struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`
// +optional // +optional
unversioned.ListMeta `json:"metadata,omitempty"` unversioned.ListMeta `json:"metadata,omitempty"`
Items []PetSet `json:"items"` Items []StatefulSet `json:"items"`
} }

View File

@ -33,14 +33,14 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
// it, but a plain int32 is more convenient in the internal type. These // it, but a plain int32 is more convenient in the internal type. These
// functions are the same as the autogenerated ones in every other way. // functions are the same as the autogenerated ones in every other way.
err := scheme.AddConversionFuncs( err := scheme.AddConversionFuncs(
Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec, Convert_v1alpha1_StatefulSetSpec_To_apps_StatefulSetSpec,
Convert_apps_PetSetSpec_To_v1alpha1_PetSetSpec, Convert_apps_StatefulSetSpec_To_v1alpha1_StatefulSetSpec,
) )
if err != nil { if err != nil {
return err return err
} }
return api.Scheme.AddFieldLabelConversionFunc("apps/v1alpha1", "PetSet", return api.Scheme.AddFieldLabelConversionFunc("apps/v1alpha1", "StatefulSet",
func(label, value string) (string, string, error) { func(label, value string) (string, string, error) {
switch label { switch label {
case "metadata.name", "metadata.namespace", "status.successful": case "metadata.name", "metadata.namespace", "status.successful":
@ -52,7 +52,7 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
) )
} }
func Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec(in *PetSetSpec, out *apps.PetSetSpec, s conversion.Scope) error { func Convert_v1alpha1_StatefulSetSpec_To_apps_StatefulSetSpec(in *StatefulSetSpec, out *apps.StatefulSetSpec, s conversion.Scope) error {
if in.Replicas != nil { if in.Replicas != nil {
out.Replicas = *in.Replicas out.Replicas = *in.Replicas
} }
@ -83,7 +83,7 @@ func Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec(in *PetSetSpec, out *apps.Pe
return nil return nil
} }
func Convert_apps_PetSetSpec_To_v1alpha1_PetSetSpec(in *apps.PetSetSpec, out *PetSetSpec, s conversion.Scope) error { func Convert_apps_StatefulSetSpec_To_v1alpha1_StatefulSetSpec(in *apps.StatefulSetSpec, out *StatefulSetSpec, s conversion.Scope) error {
out.Replicas = new(int32) out.Replicas = new(int32)
*out.Replicas = in.Replicas *out.Replicas = in.Replicas
if in.Selector != nil { if in.Selector != nil {

View File

@ -24,11 +24,11 @@ import (
func addDefaultingFuncs(scheme *runtime.Scheme) error { func addDefaultingFuncs(scheme *runtime.Scheme) error {
RegisterDefaults(scheme) RegisterDefaults(scheme)
return scheme.AddDefaultingFuncs( return scheme.AddDefaultingFuncs(
SetDefaults_PetSet, SetDefaults_StatefulSet,
) )
} }
func SetDefaults_PetSet(obj *PetSet) { func SetDefaults_StatefulSet(obj *StatefulSet) {
labels := obj.Spec.Template.Labels labels := obj.Spec.Template.Labels
if labels != nil { if labels != nil {
if obj.Spec.Selector == nil { if obj.Spec.Selector == nil {

View File

@ -37,8 +37,8 @@ var (
// Adds the list of known types to api.Scheme. // Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) error { func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion, scheme.AddKnownTypes(SchemeGroupVersion,
&PetSet{}, &StatefulSet{},
&PetSetList{}, &StatefulSetList{},
&v1.ListOptions{}, &v1.ListOptions{},
&v1.DeleteOptions{}, &v1.DeleteOptions{},
) )
@ -46,5 +46,5 @@ func addKnownTypes(scheme *runtime.Scheme) error {
return nil return nil
} }
func (obj *PetSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *StatefulSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *PetSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *StatefulSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }

View File

@ -23,30 +23,30 @@ import (
// +genclient=true // +genclient=true
// PetSet represents a set of pods with consistent identities. // StatefulSet represents a set of pods with consistent identities.
// Identities are defined as: // Identities are defined as:
// - Network: A single stable DNS and hostname. // - Network: A single stable DNS and hostname.
// - Storage: As many VolumeClaims as requested. // - Storage: As many VolumeClaims as requested.
// The PetSet guarantees that a given network identity will always // The StatefulSet guarantees that a given network identity will always
// map to the same storage identity. PetSet is currently in alpha // map to the same storage identity. StatefulSet is currently in alpha
// and subject to change without notice. // and subject to change without notice.
type PetSet struct { type StatefulSet struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`
// +optional // +optional
v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec defines the desired identities of pets in this set. // Spec defines the desired identities of pods in this set.
// +optional // +optional
Spec PetSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` Spec StatefulSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
// Status is the current status of Pets in this PetSet. This data // Status is the current status of Pods in this StatefulSet. This data
// may be out of date by some window of time. // may be out of date by some window of time.
// +optional // +optional
Status PetSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` Status StatefulSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
} }
// A PetSetSpec is the specification of a PetSet. // A StatefulSetSpec is the specification of a StatefulSet.
type PetSetSpec struct { type StatefulSetSpec struct {
// Replicas is the desired number of replicas of the given Template. // Replicas is the desired number of replicas of the given Template.
// These are replicas in the sense that they are instantiations of the // These are replicas in the sense that they are instantiations of the
// same Template, but individual replicas also have a consistent identity. // same Template, but individual replicas also have a consistent identity.
@ -62,14 +62,14 @@ type PetSetSpec struct {
Selector *unversioned.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` Selector *unversioned.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"`
// Template is the object that describes the pod that will be created if // Template is the object that describes the pod that will be created if
// insufficient replicas are detected. Each pod stamped out by the PetSet // insufficient replicas are detected. Each pod stamped out by the StatefulSet
// will fulfill this Template, but have a unique identity from the rest // will fulfill this Template, but have a unique identity from the rest
// of the PetSet. // of the StatefulSet.
Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"` Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"`
// VolumeClaimTemplates is a list of claims that pets are allowed to reference. // VolumeClaimTemplates is a list of claims that pods are allowed to reference.
// The PetSet controller is responsible for mapping network identities to // The StatefulSet controller is responsible for mapping network identities to
// claims in a way that maintains the identity of a pet. Every claim in // claims in a way that maintains the identity of a pod. Every claim in
// this list must have at least one matching (by name) volumeMount in one // this list must have at least one matching (by name) volumeMount in one
// container in the template. A claim in this list takes precedence over // container in the template. A claim in this list takes precedence over
// any volumes in the template, with the same name. // any volumes in the template, with the same name.
@ -77,16 +77,16 @@ type PetSetSpec struct {
// +optional // +optional
VolumeClaimTemplates []v1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty" protobuf:"bytes,4,rep,name=volumeClaimTemplates"` VolumeClaimTemplates []v1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty" protobuf:"bytes,4,rep,name=volumeClaimTemplates"`
// ServiceName is the name of the service that governs this PetSet. // ServiceName is the name of the service that governs this StatefulSet.
// This service must exist before the PetSet, and is responsible for // This service must exist before the StatefulSet, and is responsible for
// the network identity of the set. Pets get DNS/hostnames that follow the // the network identity of the set. Pods get DNS/hostnames that follow the
// pattern: pet-specific-string.serviceName.default.svc.cluster.local // pattern: pod-specific-string.serviceName.default.svc.cluster.local
// where "pet-specific-string" is managed by the PetSet controller. // where "pod-specific-string" is managed by the StatefulSet controller.
ServiceName string `json:"serviceName" protobuf:"bytes,5,opt,name=serviceName"` ServiceName string `json:"serviceName" protobuf:"bytes,5,opt,name=serviceName"`
} }
// PetSetStatus represents the current state of a PetSet. // StatefulSetStatus represents the current state of a StatefulSet.
type PetSetStatus struct { type StatefulSetStatus struct {
// most recent generation observed by this autoscaler. // most recent generation observed by this autoscaler.
// +optional // +optional
ObservedGeneration *int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` ObservedGeneration *int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"`
@ -95,10 +95,10 @@ type PetSetStatus struct {
Replicas int32 `json:"replicas" protobuf:"varint,2,opt,name=replicas"` Replicas int32 `json:"replicas" protobuf:"varint,2,opt,name=replicas"`
} }
// PetSetList is a collection of PetSets. // StatefulSetList is a collection of StatefulSets.
type PetSetList struct { type StatefulSetList struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`
// +optional // +optional
unversioned.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` unversioned.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Items []PetSet `json:"items" protobuf:"bytes,2,rep,name=items"` Items []StatefulSet `json:"items" protobuf:"bytes,2,rep,name=items"`
} }

View File

@ -28,24 +28,24 @@ import (
"k8s.io/kubernetes/pkg/util/validation/field" "k8s.io/kubernetes/pkg/util/validation/field"
) )
// ValidatePetSetName can be used to check whether the given PetSet name is valid. // ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid.
// Prefix indicates this name will be used as part of generation, in which case // Prefix indicates this name will be used as part of generation, in which case
// trailing dashes are allowed. // trailing dashes are allowed.
func ValidatePetSetName(name string, prefix bool) []string { func ValidateStatefulSetName(name string, prefix bool) []string {
// TODO: Validate that there's name for the suffix inserted by the pets. // TODO: Validate that there's name for the suffix inserted by the pods.
// Currently this is just "-index". In the future we may allow a user // Currently this is just "-index". In the future we may allow a user
// specified list of suffixes and we need to validate the longest one. // specified list of suffixes and we need to validate the longest one.
return apivalidation.NameIsDNSSubdomain(name, prefix) return apivalidation.NameIsDNSSubdomain(name, prefix)
} }
// Validates the given template and ensures that it is in accordance with the desired selector. // Validates the given template and ensures that it is in accordance with the desired selector.
func ValidatePodTemplateSpecForPetSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path) field.ErrorList { func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if template == nil { if template == nil {
allErrs = append(allErrs, field.Required(fldPath, "")) allErrs = append(allErrs, field.Required(fldPath, ""))
} else { } else {
if !selector.Empty() { if !selector.Empty() {
// Verify that the PetSet selector matches the labels in template. // Verify that the StatefulSet selector matches the labels in template.
labels := labels.Set(template.Labels) labels := labels.Set(template.Labels)
if !selector.Matches(labels) { if !selector.Matches(labels) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`"))
@ -62,8 +62,8 @@ func ValidatePodTemplateSpecForPetSet(template *api.PodTemplateSpec, selector la
return allErrs return allErrs
} }
// ValidatePetSetSpec tests if required fields in the PetSet spec are set. // ValidateStatefulSetSpec tests if required fields in the StatefulSet spec are set.
func ValidatePetSetSpec(spec *apps.PetSetSpec, fldPath *field.Path) field.ErrorList { func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
@ -72,7 +72,7 @@ func ValidatePetSetSpec(spec *apps.PetSetSpec, fldPath *field.Path) field.ErrorL
} else { } else {
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is not valid for petset.")) allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is not valid for statefulset."))
} }
} }
@ -80,7 +80,7 @@ func ValidatePetSetSpec(spec *apps.PetSetSpec, fldPath *field.Path) field.ErrorL
if err != nil { if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "")) allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, ""))
} else { } else {
allErrs = append(allErrs, ValidatePodTemplateSpecForPetSet(&spec.Template, selector, fldPath.Child("template"))...) allErrs = append(allErrs, ValidatePodTemplateSpecForStatefulSet(&spec.Template, selector, fldPath.Child("template"))...)
} }
if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
@ -90,42 +90,42 @@ func ValidatePetSetSpec(spec *apps.PetSetSpec, fldPath *field.Path) field.ErrorL
return allErrs return allErrs
} }
// ValidatePetSet validates a PetSet. // ValidateStatefulSet validates a StatefulSet.
func ValidatePetSet(petSet *apps.PetSet) field.ErrorList { func ValidateStatefulSet(statefulSet *apps.StatefulSet) field.ErrorList {
allErrs := apivalidation.ValidateObjectMeta(&petSet.ObjectMeta, true, ValidatePetSetName, field.NewPath("metadata")) allErrs := apivalidation.ValidateObjectMeta(&statefulSet.ObjectMeta, true, ValidateStatefulSetName, field.NewPath("metadata"))
allErrs = append(allErrs, ValidatePetSetSpec(&petSet.Spec, field.NewPath("spec"))...) allErrs = append(allErrs, ValidateStatefulSetSpec(&statefulSet.Spec, field.NewPath("spec"))...)
return allErrs return allErrs
} }
// ValidatePetSetUpdate tests if required fields in the PetSet are set. // ValidateStatefulSetUpdate tests if required fields in the StatefulSet are set.
func ValidatePetSetUpdate(petSet, oldPetSet *apps.PetSet) field.ErrorList { func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) field.ErrorList {
allErrs := apivalidation.ValidateObjectMetaUpdate(&petSet.ObjectMeta, &oldPetSet.ObjectMeta, field.NewPath("metadata")) allErrs := apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata"))
// TODO: For now we're taking the safe route and disallowing all updates to // TODO: For now we're taking the safe route and disallowing all updates to
// spec except for Replicas, for scaling, and Template.Spec.containers.image // spec except for Replicas, for scaling, and Template.Spec.containers.image
// for rolling-update. Enable others on a case by case basis. // for rolling-update. Enable others on a case by case basis.
restoreReplicas := petSet.Spec.Replicas restoreReplicas := statefulSet.Spec.Replicas
petSet.Spec.Replicas = oldPetSet.Spec.Replicas statefulSet.Spec.Replicas = oldStatefulSet.Spec.Replicas
restoreContainers := petSet.Spec.Template.Spec.Containers restoreContainers := statefulSet.Spec.Template.Spec.Containers
petSet.Spec.Template.Spec.Containers = oldPetSet.Spec.Template.Spec.Containers statefulSet.Spec.Template.Spec.Containers = oldStatefulSet.Spec.Template.Spec.Containers
if !reflect.DeepEqual(petSet.Spec, oldPetSet.Spec) { if !reflect.DeepEqual(statefulSet.Spec, oldStatefulSet.Spec) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to petset spec for fields other than 'replicas' are forbidden.")) allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas' are forbidden."))
} }
petSet.Spec.Replicas = restoreReplicas statefulSet.Spec.Replicas = restoreReplicas
petSet.Spec.Template.Spec.Containers = restoreContainers statefulSet.Spec.Template.Spec.Containers = restoreContainers
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(petSet.Spec.Replicas), field.NewPath("spec", "replicas"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(statefulSet.Spec.Replicas), field.NewPath("spec", "replicas"))...)
containerErrs, _ := apivalidation.ValidateContainerUpdates(petSet.Spec.Template.Spec.Containers, oldPetSet.Spec.Template.Spec.Containers, field.NewPath("spec").Child("template").Child("containers")) containerErrs, _ := apivalidation.ValidateContainerUpdates(statefulSet.Spec.Template.Spec.Containers, oldStatefulSet.Spec.Template.Spec.Containers, field.NewPath("spec").Child("template").Child("containers"))
allErrs = append(allErrs, containerErrs...) allErrs = append(allErrs, containerErrs...)
return allErrs return allErrs
} }
// ValidatePetSetStatusUpdate tests if required fields in the PetSet are set. // ValidateStatefulSetStatusUpdate tests if required fields in the StatefulSet are set.
func ValidatePetSetStatusUpdate(petSet, oldPetSet *apps.PetSet) field.ErrorList { func ValidateStatefulSetStatusUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&petSet.ObjectMeta, &oldPetSet.ObjectMeta, field.NewPath("metadata"))...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata"))...)
// TODO: Validate status. // TODO: Validate status.
return allErrs return allErrs
} }

View File

@ -25,7 +25,7 @@ import (
"k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps"
) )
func TestValidatePetSet(t *testing.T) { func TestValidateStatefulSet(t *testing.T) {
validLabels := map[string]string{"a": "b"} validLabels := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{ validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
@ -51,65 +51,65 @@ func TestValidatePetSet(t *testing.T) {
}, },
}, },
} }
successCases := []apps.PetSet{ successCases := []apps.StatefulSet{
{ {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
{ {
ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
} }
for _, successCase := range successCases { for _, successCase := range successCases {
if errs := ValidatePetSet(&successCase); len(errs) != 0 { if errs := ValidateStatefulSet(&successCase); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
errorCases := map[string]apps.PetSet{ errorCases := map[string]apps.StatefulSet{
"zero-length ID": { "zero-length ID": {
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
"missing-namespace": { "missing-namespace": {
ObjectMeta: api.ObjectMeta{Name: "abc-123"}, ObjectMeta: api.ObjectMeta{Name: "abc-123"},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
"empty selector": { "empty selector": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
"selector_doesnt_match": { "selector_doesnt_match": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
"invalid manifest": { "invalid manifest": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
}, },
}, },
"negative_replicas": { "negative_replicas": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: -1, Replicas: -1,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
}, },
@ -122,7 +122,7 @@ func TestValidatePetSet(t *testing.T) {
"NoUppercaseOrSpecialCharsLike=Equals": "bar", "NoUppercaseOrSpecialCharsLike=Equals": "bar",
}, },
}, },
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
@ -135,7 +135,7 @@ func TestValidatePetSet(t *testing.T) {
"NoUppercaseOrSpecialCharsLike=Equals": "bar", "NoUppercaseOrSpecialCharsLike=Equals": "bar",
}, },
}, },
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Template: invalidPodTemplate.Template, Template: invalidPodTemplate.Template,
}, },
}, },
@ -147,7 +147,7 @@ func TestValidatePetSet(t *testing.T) {
"NoUppercaseOrSpecialCharsLike=Equals": "bar", "NoUppercaseOrSpecialCharsLike=Equals": "bar",
}, },
}, },
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
@ -157,7 +157,7 @@ func TestValidatePetSet(t *testing.T) {
Name: "abc-123", Name: "abc-123",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
Spec: api.PodSpec{ Spec: api.PodSpec{
@ -176,7 +176,7 @@ func TestValidatePetSet(t *testing.T) {
Name: "abc-123", Name: "abc-123",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
Spec: api.PodSpec{ Spec: api.PodSpec{
@ -192,7 +192,7 @@ func TestValidatePetSet(t *testing.T) {
}, },
} }
for k, v := range errorCases { for k, v := range errorCases {
errs := ValidatePetSet(&v) errs := ValidateStatefulSet(&v)
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %s", k)
} }
@ -215,7 +215,7 @@ func TestValidatePetSet(t *testing.T) {
} }
} }
func TestValidatePetSetUpdate(t *testing.T) { func TestValidateStatefulSetUpdate(t *testing.T) {
validLabels := map[string]string{"a": "b"} validLabels := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{ validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
@ -255,21 +255,21 @@ func TestValidatePetSetUpdate(t *testing.T) {
}, },
} }
type psUpdateTest struct { type psUpdateTest struct {
old apps.PetSet old apps.StatefulSet
update apps.PetSet update apps.StatefulSet
} }
successCases := []psUpdateTest{ successCases := []psUpdateTest{
{ {
old: apps.PetSet{ old: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
update: apps.PetSet{ update: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: 3, Replicas: 3,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
@ -280,22 +280,22 @@ func TestValidatePetSetUpdate(t *testing.T) {
for _, successCase := range successCases { for _, successCase := range successCases {
successCase.old.ObjectMeta.ResourceVersion = "1" successCase.old.ObjectMeta.ResourceVersion = "1"
successCase.update.ObjectMeta.ResourceVersion = "1" successCase.update.ObjectMeta.ResourceVersion = "1"
if errs := ValidatePetSetUpdate(&successCase.update, &successCase.old); len(errs) != 0 { if errs := ValidateStatefulSetUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
errorCases := map[string]psUpdateTest{ errorCases := map[string]psUpdateTest{
"more than one read/write": { "more than one read/write": {
old: apps.PetSet{ old: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
update: apps.PetSet{ update: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: 2, Replicas: 2,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: readWriteVolumePodTemplate.Template, Template: readWriteVolumePodTemplate.Template,
@ -303,16 +303,16 @@ func TestValidatePetSetUpdate(t *testing.T) {
}, },
}, },
"updates to a field other than spec.Replicas": { "updates to a field other than spec.Replicas": {
old: apps.PetSet{ old: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
update: apps.PetSet{ update: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: 1, Replicas: 1,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: readWriteVolumePodTemplate.Template, Template: readWriteVolumePodTemplate.Template,
@ -320,16 +320,16 @@ func TestValidatePetSetUpdate(t *testing.T) {
}, },
}, },
"invalid selector": { "invalid selector": {
old: apps.PetSet{ old: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
update: apps.PetSet{ update: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: 2, Replicas: 2,
Selector: &unversioned.LabelSelector{MatchLabels: invalidLabels}, Selector: &unversioned.LabelSelector{MatchLabels: invalidLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
@ -337,16 +337,16 @@ func TestValidatePetSetUpdate(t *testing.T) {
}, },
}, },
"invalid pod": { "invalid pod": {
old: apps.PetSet{ old: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
update: apps.PetSet{ update: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: 2, Replicas: 2,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: invalidPodTemplate.Template, Template: invalidPodTemplate.Template,
@ -354,16 +354,16 @@ func TestValidatePetSetUpdate(t *testing.T) {
}, },
}, },
"negative replicas": { "negative replicas": {
old: apps.PetSet{ old: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
}, },
update: apps.PetSet{ update: apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: -1, Replicas: -1,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
@ -372,7 +372,7 @@ func TestValidatePetSetUpdate(t *testing.T) {
}, },
} }
for testName, errorCase := range errorCases { for testName, errorCase := range errorCases {
if errs := ValidatePetSetUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { if errs := ValidateStatefulSetUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
t.Errorf("expected failure: %s", testName) t.Errorf("expected failure: %s", testName)
} }
} }

View File

@ -364,13 +364,13 @@ func (s *StoreToPVFetcher) GetPersistentVolumeInfo(id string) (*api.PersistentVo
return o.(*api.PersistentVolume), nil return o.(*api.PersistentVolume), nil
} }
// StoreToPetSetLister gives a store List and Exists methods. The store must contain only PetSets. // StoreToStatefulSetLister gives a store List and Exists methods. The store must contain only StatefulSets.
type StoreToPetSetLister struct { type StoreToStatefulSetLister struct {
Store Store
} }
// Exists checks if the given PetSet exists in the store. // Exists checks if the given StatefulSet exists in the store.
func (s *StoreToPetSetLister) Exists(ps *apps.PetSet) (bool, error) { func (s *StoreToStatefulSetLister) Exists(ps *apps.StatefulSet) (bool, error) {
_, exists, err := s.Store.Get(ps) _, exists, err := s.Store.Get(ps)
if err != nil { if err != nil {
return false, err return false, err
@ -378,35 +378,35 @@ func (s *StoreToPetSetLister) Exists(ps *apps.PetSet) (bool, error) {
return exists, nil return exists, nil
} }
// List lists all PetSets in the store. // List lists all StatefulSets in the store.
func (s *StoreToPetSetLister) List() (psList []apps.PetSet, err error) { func (s *StoreToStatefulSetLister) List() (psList []apps.StatefulSet, err error) {
for _, ps := range s.Store.List() { for _, ps := range s.Store.List() {
psList = append(psList, *(ps.(*apps.PetSet))) psList = append(psList, *(ps.(*apps.StatefulSet)))
} }
return psList, nil return psList, nil
} }
type storePetSetsNamespacer struct { type storeStatefulSetsNamespacer struct {
store Store store Store
namespace string namespace string
} }
func (s *StoreToPetSetLister) PetSets(namespace string) storePetSetsNamespacer { func (s *StoreToStatefulSetLister) StatefulSets(namespace string) storeStatefulSetsNamespacer {
return storePetSetsNamespacer{s.Store, namespace} return storeStatefulSetsNamespacer{s.Store, namespace}
} }
// GetPodPetSets returns a list of PetSets managing a pod. Returns an error only if no matching PetSets are found. // GetPodStatefulSets returns a list of StatefulSets managing a pod. Returns an error only if no matching StatefulSets are found.
func (s *StoreToPetSetLister) GetPodPetSets(pod *api.Pod) (psList []apps.PetSet, err error) { func (s *StoreToStatefulSetLister) GetPodStatefulSets(pod *api.Pod) (psList []apps.StatefulSet, err error) {
var selector labels.Selector var selector labels.Selector
var ps apps.PetSet var ps apps.StatefulSet
if len(pod.Labels) == 0 { if len(pod.Labels) == 0 {
err = fmt.Errorf("no PetSets found for pod %v because it has no labels", pod.Name) err = fmt.Errorf("no StatefulSets found for pod %v because it has no labels", pod.Name)
return return
} }
for _, m := range s.Store.List() { for _, m := range s.Store.List() {
ps = *m.(*apps.PetSet) ps = *m.(*apps.StatefulSet)
if ps.Namespace != pod.Namespace { if ps.Namespace != pod.Namespace {
continue continue
} }
@ -416,14 +416,14 @@ func (s *StoreToPetSetLister) GetPodPetSets(pod *api.Pod) (psList []apps.PetSet,
return return
} }
// If a PetSet with a nil or empty selector creeps in, it should match nothing, not everything. // If a StatefulSet with a nil or empty selector creeps in, it should match nothing, not everything.
if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) { if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) {
continue continue
} }
psList = append(psList, ps) psList = append(psList, ps)
} }
if len(psList) == 0 { if len(psList) == 0 {
err = fmt.Errorf("could not find PetSet for pod %s in namespace %s with labels: %v", pod.Name, pod.Namespace, pod.Labels) err = fmt.Errorf("could not find StatefulSet for pod %s in namespace %s with labels: %v", pod.Name, pod.Namespace, pod.Labels)
} }
return return
} }

View File

@ -0,0 +1,55 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unversioned
import (
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/client/restclient"
)
type AppsInterface interface {
StatefulSetNamespacer
}
// AppsClient is used to interact with Kubernetes batch features.
type AppsClient struct {
*restclient.RESTClient
}
func (c *AppsClient) StatefulSets(namespace string) StatefulSetInterface {
return newStatefulSet(c, namespace)
}
func NewApps(c *restclient.Config) (*AppsClient, error) {
config := *c
if err := setGroupDefaults(apps.GroupName, &config); err != nil {
return nil, err
}
client, err := restclient.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &AppsClient{client}, nil
}
func NewAppsOrDie(c *restclient.Config) *AppsClient {
client, err := NewApps(c)
if err != nil {
panic(err)
}
return client
}

View File

@ -77,11 +77,11 @@ func ReplicaSetHasDesiredReplicas(rsClient extensionsclient.ReplicaSetsGetter, r
} }
} }
// PetSetHasDesiredPets returns a conditon that checks the number of petset replicas // StatefulSetHasDesiredPets returns a conditon that checks the number of petset replicas
func PetSetHasDesiredPets(psClient appsclient.PetSetsGetter, petset *apps.PetSet) wait.ConditionFunc { func StatefulSetHasDesiredPets(psClient appsclient.StatefulSetsGetter, petset *apps.StatefulSet) wait.ConditionFunc {
// TODO: Differentiate between 0 pets and a really quick scale down using generation. // TODO: Differentiate between 0 pets and a really quick scale down using generation.
return func() (bool, error) { return func() (bool, error) {
ps, err := psClient.PetSets(petset.Namespace).Get(petset.Name) ps, err := psClient.StatefulSets(petset.Namespace).Get(petset.Name)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -0,0 +1,100 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unversioned
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/watch"
)
// StatefulSetNamespacer has methods to work with StatefulSet resources in a namespace
type StatefulSetNamespacer interface {
StatefulSets(namespace string) StatefulSetInterface
}
// StatefulSetInterface exposes methods to work on StatefulSet resources.
type StatefulSetInterface interface {
List(opts api.ListOptions) (*apps.StatefulSetList, error)
Get(name string) (*apps.StatefulSet, error)
Create(statefulSet *apps.StatefulSet) (*apps.StatefulSet, error)
Update(statefulSet *apps.StatefulSet) (*apps.StatefulSet, error)
Delete(name string, options *api.DeleteOptions) error
Watch(opts api.ListOptions) (watch.Interface, error)
UpdateStatus(statefulSet *apps.StatefulSet) (*apps.StatefulSet, error)
}
// statefulSet implements StatefulSetNamespacer interface
type statefulSet struct {
r *AppsClient
ns string
}
// newStatefulSet returns a statefulSet
func newStatefulSet(c *AppsClient, namespace string) *statefulSet {
return &statefulSet{c, namespace}
}
// List returns a list of statefulSet that match the label and field selectors.
func (c *statefulSet) List(opts api.ListOptions) (result *apps.StatefulSetList, err error) {
result = &apps.StatefulSetList{}
err = c.r.Get().Namespace(c.ns).Resource("statefulsets").VersionedParams(&opts, api.ParameterCodec).Do().Into(result)
return
}
// Get returns information about a particular statefulSet.
func (c *statefulSet) Get(name string) (result *apps.StatefulSet, err error) {
result = &apps.StatefulSet{}
err = c.r.Get().Namespace(c.ns).Resource("statefulsets").Name(name).Do().Into(result)
return
}
// Create creates a new statefulSet.
func (c *statefulSet) Create(statefulSet *apps.StatefulSet) (result *apps.StatefulSet, err error) {
result = &apps.StatefulSet{}
err = c.r.Post().Namespace(c.ns).Resource("statefulsets").Body(statefulSet).Do().Into(result)
return
}
// Update updates an existing statefulSet.
func (c *statefulSet) Update(statefulSet *apps.StatefulSet) (result *apps.StatefulSet, err error) {
result = &apps.StatefulSet{}
err = c.r.Put().Namespace(c.ns).Resource("statefulsets").Name(statefulSet.Name).Body(statefulSet).Do().Into(result)
return
}
// Delete deletes a statefulSet, returns error if one occurs.
func (c *statefulSet) Delete(name string, options *api.DeleteOptions) (err error) {
return c.r.Delete().Namespace(c.ns).Resource("statefulsets").Name(name).Body(options).Do().Error()
}
// Watch returns a watch.Interface that watches the requested statefulSet.
func (c *statefulSet) Watch(opts api.ListOptions) (watch.Interface, error) {
return c.r.Get().
Prefix("watch").
Namespace(c.ns).
Resource("statefulsets").
VersionedParams(&opts, api.ParameterCodec).
Watch()
}
// UpdateStatus takes the name of the statefulSet and the new status. Returns the server's representation of the statefulSet, and an error, if it occurs.
func (c *statefulSet) UpdateStatus(statefulSet *apps.StatefulSet) (result *apps.StatefulSet, err error) {
result = &apps.StatefulSet{}
err = c.r.Put().Namespace(c.ns).Resource("statefulsets").Name(statefulSet.Name).SubResource("status").Body(statefulSet).Do().Into(result)
return
}

View File

@ -0,0 +1,165 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unversioned_test
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/client/unversioned/testclient/simple"
)
func getStatefulSetResourceName() string {
return "statefulsets"
}
func TestListStatefulSets(t *testing.T) {
ns := api.NamespaceAll
c := &simple.Client{
Request: simple.Request{
Method: "GET",
Path: testapi.Apps.ResourcePath(getStatefulSetResourceName(), ns, ""),
},
Response: simple.Response{StatusCode: 200,
Body: &apps.StatefulSetList{
Items: []apps.StatefulSet{
{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: apps.StatefulSetSpec{
Replicas: 2,
Template: api.PodTemplateSpec{},
},
},
},
},
},
}
receivedRSList, err := c.Setup(t).Apps().StatefulSets(ns).List(api.ListOptions{})
c.Validate(t, receivedRSList, err)
}
func TestGetStatefulSet(t *testing.T) {
ns := api.NamespaceDefault
c := &simple.Client{
Request: simple.Request{Method: "GET", Path: testapi.Apps.ResourcePath(getStatefulSetResourceName(), ns, "foo"), Query: simple.BuildQueryValues(nil)},
Response: simple.Response{
StatusCode: 200,
Body: &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: apps.StatefulSetSpec{
Replicas: 2,
Template: api.PodTemplateSpec{},
},
},
},
}
receivedRS, err := c.Setup(t).Apps().StatefulSets(ns).Get("foo")
c.Validate(t, receivedRS, err)
}
func TestGetStatefulSetWithNoName(t *testing.T) {
ns := api.NamespaceDefault
c := &simple.Client{Error: true}
receivedPod, err := c.Setup(t).Apps().StatefulSets(ns).Get("")
if (err != nil) && (err.Error() != simple.NameRequiredError) {
t.Errorf("Expected error: %v, but got %v", simple.NameRequiredError, err)
}
c.Validate(t, receivedPod, err)
}
func TestUpdateStatefulSet(t *testing.T) {
ns := api.NamespaceDefault
requestRS := &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
}
c := &simple.Client{
Request: simple.Request{Method: "PUT", Path: testapi.Apps.ResourcePath(getStatefulSetResourceName(), ns, "foo"), Query: simple.BuildQueryValues(nil)},
Response: simple.Response{
StatusCode: 200,
Body: &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: apps.StatefulSetSpec{
Replicas: 2,
Template: api.PodTemplateSpec{},
},
},
},
}
receivedRS, err := c.Setup(t).Apps().StatefulSets(ns).Update(requestRS)
c.Validate(t, receivedRS, err)
}
func TestDeleteStatefulSet(t *testing.T) {
ns := api.NamespaceDefault
c := &simple.Client{
Request: simple.Request{Method: "DELETE", Path: testapi.Apps.ResourcePath(getStatefulSetResourceName(), ns, "foo"), Query: simple.BuildQueryValues(nil)},
Response: simple.Response{StatusCode: 200},
}
err := c.Setup(t).Apps().StatefulSets(ns).Delete("foo", nil)
c.Validate(t, nil, err)
}
func TestCreateStatefulSet(t *testing.T) {
ns := api.NamespaceDefault
requestRS := &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "foo"},
}
c := &simple.Client{
Request: simple.Request{Method: "POST", Path: testapi.Apps.ResourcePath(getStatefulSetResourceName(), ns, ""), Body: requestRS, Query: simple.BuildQueryValues(nil)},
Response: simple.Response{
StatusCode: 200,
Body: &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: apps.StatefulSetSpec{
Replicas: 2,
Template: api.PodTemplateSpec{},
},
},
},
}
receivedRS, err := c.Setup(t).Apps().StatefulSets(ns).Create(requestRS)
c.Validate(t, receivedRS, err)
}
// TODO: Test Status actions.

View File

@ -0,0 +1,83 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testclient
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/watch"
)
// FakeStatefulSets implements StatefulSetsInterface. Meant to be embedded into a struct to get a default
// implementation. This makes faking out just the method you want to test easier.
type FakeStatefulSets struct {
Fake *FakeApps
Namespace string
}
func (c *FakeStatefulSets) Get(name string) (*apps.StatefulSet, error) {
obj, err := c.Fake.Invokes(NewGetAction("statefulsets", c.Namespace, name), &apps.StatefulSet{})
if obj == nil {
return nil, err
}
return obj.(*apps.StatefulSet), err
}
func (c *FakeStatefulSets) List(opts api.ListOptions) (*apps.StatefulSetList, error) {
obj, err := c.Fake.Invokes(NewListAction("statefulsets", c.Namespace, opts), &apps.StatefulSetList{})
if obj == nil {
return nil, err
}
return obj.(*apps.StatefulSetList), err
}
func (c *FakeStatefulSets) Create(rs *apps.StatefulSet) (*apps.StatefulSet, error) {
obj, err := c.Fake.Invokes(NewCreateAction("statefulsets", c.Namespace, rs), rs)
if obj == nil {
return nil, err
}
return obj.(*apps.StatefulSet), err
}
func (c *FakeStatefulSets) Update(rs *apps.StatefulSet) (*apps.StatefulSet, error) {
obj, err := c.Fake.Invokes(NewUpdateAction("statefulsets", c.Namespace, rs), rs)
if obj == nil {
return nil, err
}
return obj.(*apps.StatefulSet), err
}
func (c *FakeStatefulSets) Delete(name string, options *api.DeleteOptions) error {
_, err := c.Fake.Invokes(NewDeleteAction("statefulsets", c.Namespace, name), &apps.StatefulSet{})
return err
}
func (c *FakeStatefulSets) Watch(opts api.ListOptions) (watch.Interface, error) {
return c.Fake.InvokesWatch(NewWatchAction("statefulsets", c.Namespace, opts))
}
func (c *FakeStatefulSets) UpdateStatus(rs *apps.StatefulSet) (result *apps.StatefulSet, err error) {
obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("statefulsets", "status", c.Namespace, rs), rs)
if obj == nil {
return nil, err
}
return obj.(*apps.StatefulSet), err
}

View File

@ -0,0 +1,533 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testclient
import (
"fmt"
"sync"
"github.com/emicklei/go-restful/swagger"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/typed/discovery"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/pkg/watch"
)
// NewSimpleFake returns a client that will respond with the provided objects
func NewSimpleFake(objects ...runtime.Object) *Fake {
o := NewObjects(api.Scheme, api.Codecs.UniversalDecoder())
for _, obj := range objects {
if err := o.Add(obj); err != nil {
panic(err)
}
}
fakeClient := &Fake{}
fakeClient.AddReactor("*", "*", ObjectReaction(o, registered.RESTMapper()))
fakeClient.AddWatchReactor("*", DefaultWatchReactor(watch.NewFake(), nil))
return fakeClient
}
// Fake implements client.Interface. Meant to be embedded into a struct to get a default
// implementation. This makes faking out just the method you want to test easier.
type Fake struct {
sync.RWMutex
actions []Action // these may be castable to other types, but "Action" is the minimum
// ReactionChain is the list of reactors that will be attempted for every request in the order they are tried
ReactionChain []Reactor
// WatchReactionChain is the list of watch reactors that will be attempted for every request in the order they are tried
WatchReactionChain []WatchReactor
// ProxyReactionChain is the list of proxy reactors that will be attempted for every request in the order they are tried
ProxyReactionChain []ProxyReactor
Resources map[string]*unversioned.APIResourceList
}
// Reactor is an interface to allow the composition of reaction functions.
type Reactor interface {
// Handles indicates whether or not this Reactor deals with a given action
Handles(action Action) bool
// React handles the action and returns results. It may choose to delegate by indicated handled=false
React(action Action) (handled bool, ret runtime.Object, err error)
}
// WatchReactor is an interface to allow the composition of watch functions.
type WatchReactor interface {
// Handles indicates whether or not this Reactor deals with a given action
Handles(action Action) bool
// React handles a watch action and returns results. It may choose to delegate by indicated handled=false
React(action Action) (handled bool, ret watch.Interface, err error)
}
// ProxyReactor is an interface to allow the composition of proxy get functions.
type ProxyReactor interface {
// Handles indicates whether or not this Reactor deals with a given action
Handles(action Action) bool
// React handles a watch action and returns results. It may choose to delegate by indicated handled=false
React(action Action) (handled bool, ret restclient.ResponseWrapper, err error)
}
// ReactionFunc is a function that returns an object or error for a given Action. If "handled" is false,
// then the test client will continue ignore the results and continue to the next ReactionFunc
type ReactionFunc func(action Action) (handled bool, ret runtime.Object, err error)
// WatchReactionFunc is a function that returns a watch interface. If "handled" is false,
// then the test client will continue ignore the results and continue to the next ReactionFunc
type WatchReactionFunc func(action Action) (handled bool, ret watch.Interface, err error)
// ProxyReactionFunc is a function that returns a ResponseWrapper interface for a given Action. If "handled" is false,
// then the test client will continue ignore the results and continue to the next ProxyReactionFunc
type ProxyReactionFunc func(action Action) (handled bool, ret restclient.ResponseWrapper, err error)
// AddReactor appends a reactor to the end of the chain
func (c *Fake) AddReactor(verb, resource string, reaction ReactionFunc) {
c.ReactionChain = append(c.ReactionChain, &SimpleReactor{verb, resource, reaction})
}
// PrependReactor adds a reactor to the beginning of the chain
func (c *Fake) PrependReactor(verb, resource string, reaction ReactionFunc) {
c.ReactionChain = append([]Reactor{&SimpleReactor{verb, resource, reaction}}, c.ReactionChain...)
}
// AddWatchReactor appends a reactor to the end of the chain
func (c *Fake) AddWatchReactor(resource string, reaction WatchReactionFunc) {
c.WatchReactionChain = append(c.WatchReactionChain, &SimpleWatchReactor{resource, reaction})
}
// PrependWatchReactor adds a reactor to the beginning of the chain
func (c *Fake) PrependWatchReactor(resource string, reaction WatchReactionFunc) {
c.WatchReactionChain = append([]WatchReactor{&SimpleWatchReactor{resource, reaction}}, c.WatchReactionChain...)
}
// AddProxyReactor appends a reactor to the end of the chain
func (c *Fake) AddProxyReactor(resource string, reaction ProxyReactionFunc) {
c.ProxyReactionChain = append(c.ProxyReactionChain, &SimpleProxyReactor{resource, reaction})
}
// PrependProxyReactor adds a reactor to the beginning of the chain
func (c *Fake) PrependProxyReactor(resource string, reaction ProxyReactionFunc) {
c.ProxyReactionChain = append([]ProxyReactor{&SimpleProxyReactor{resource, reaction}}, c.ProxyReactionChain...)
}
// Invokes records the provided Action and then invokes the ReactFn (if provided).
// defaultReturnObj is expected to be of the same type a normal call would return.
func (c *Fake) Invokes(action Action, defaultReturnObj runtime.Object) (runtime.Object, error) {
c.Lock()
defer c.Unlock()
c.actions = append(c.actions, action)
for _, reactor := range c.ReactionChain {
if !reactor.Handles(action) {
continue
}
handled, ret, err := reactor.React(action)
if !handled {
continue
}
return ret, err
}
return defaultReturnObj, nil
}
// InvokesWatch records the provided Action and then invokes the ReactFn (if provided).
func (c *Fake) InvokesWatch(action Action) (watch.Interface, error) {
c.Lock()
defer c.Unlock()
c.actions = append(c.actions, action)
for _, reactor := range c.WatchReactionChain {
if !reactor.Handles(action) {
continue
}
handled, ret, err := reactor.React(action)
if !handled {
continue
}
return ret, err
}
return nil, fmt.Errorf("unhandled watch: %#v", action)
}
// InvokesProxy records the provided Action and then invokes the ReactFn (if provided).
func (c *Fake) InvokesProxy(action Action) restclient.ResponseWrapper {
c.Lock()
defer c.Unlock()
c.actions = append(c.actions, action)
for _, reactor := range c.ProxyReactionChain {
if !reactor.Handles(action) {
continue
}
handled, ret, err := reactor.React(action)
if !handled || err != nil {
continue
}
return ret
}
return nil
}
// ClearActions clears the history of actions called on the fake client
func (c *Fake) ClearActions() {
c.Lock()
defer c.Unlock()
c.actions = make([]Action, 0)
}
// Actions returns a chronologically ordered slice fake actions called on the fake client
func (c *Fake) Actions() []Action {
c.RLock()
defer c.RUnlock()
fa := make([]Action, len(c.actions))
copy(fa, c.actions)
return fa
}
func (c *Fake) LimitRanges(namespace string) client.LimitRangeInterface {
return &FakeLimitRanges{Fake: c, Namespace: namespace}
}
func (c *Fake) ResourceQuotas(namespace string) client.ResourceQuotaInterface {
return &FakeResourceQuotas{Fake: c, Namespace: namespace}
}
func (c *Fake) ReplicationControllers(namespace string) client.ReplicationControllerInterface {
return &FakeReplicationControllers{Fake: c, Namespace: namespace}
}
func (c *Fake) Nodes() client.NodeInterface {
return &FakeNodes{Fake: c}
}
func (c *Fake) PodSecurityPolicies() client.PodSecurityPolicyInterface {
return &FakePodSecurityPolicy{Fake: c}
}
func (c *Fake) Events(namespace string) client.EventInterface {
return &FakeEvents{Fake: c, Namespace: namespace}
}
func (c *Fake) Endpoints(namespace string) client.EndpointsInterface {
return &FakeEndpoints{Fake: c, Namespace: namespace}
}
func (c *Fake) PersistentVolumes() client.PersistentVolumeInterface {
return &FakePersistentVolumes{Fake: c}
}
func (c *Fake) PersistentVolumeClaims(namespace string) client.PersistentVolumeClaimInterface {
return &FakePersistentVolumeClaims{Fake: c, Namespace: namespace}
}
func (c *Fake) Pods(namespace string) client.PodInterface {
return &FakePods{Fake: c, Namespace: namespace}
}
func (c *Fake) PodTemplates(namespace string) client.PodTemplateInterface {
return &FakePodTemplates{Fake: c, Namespace: namespace}
}
func (c *Fake) Services(namespace string) client.ServiceInterface {
return &FakeServices{Fake: c, Namespace: namespace}
}
func (c *Fake) ServiceAccounts(namespace string) client.ServiceAccountsInterface {
return &FakeServiceAccounts{Fake: c, Namespace: namespace}
}
func (c *Fake) Secrets(namespace string) client.SecretsInterface {
return &FakeSecrets{Fake: c, Namespace: namespace}
}
func (c *Fake) Namespaces() client.NamespaceInterface {
return &FakeNamespaces{Fake: c}
}
func (c *Fake) Apps() client.AppsInterface {
return &FakeApps{c}
}
func (c *Fake) Authorization() client.AuthorizationInterface {
return &FakeAuthorization{c}
}
func (c *Fake) Autoscaling() client.AutoscalingInterface {
return &FakeAutoscaling{c}
}
func (c *Fake) Batch() client.BatchInterface {
return &FakeBatch{c}
}
func (c *Fake) Certificates() client.CertificatesInterface {
return &FakeCertificates{c}
}
func (c *Fake) Extensions() client.ExtensionsInterface {
return &FakeExperimental{c}
}
func (c *Fake) Discovery() discovery.DiscoveryInterface {
return &FakeDiscovery{c}
}
func (c *Fake) ComponentStatuses() client.ComponentStatusInterface {
return &FakeComponentStatuses{Fake: c}
}
func (c *Fake) ConfigMaps(namespace string) client.ConfigMapsInterface {
return &FakeConfigMaps{Fake: c, Namespace: namespace}
}
func (c *Fake) Rbac() client.RbacInterface {
return &FakeRbac{Fake: c}
}
func (c *Fake) Storage() client.StorageInterface {
return &FakeStorage{Fake: c}
}
func (c *Fake) Authentication() client.AuthenticationInterface {
return &FakeAuthentication{Fake: c}
}
// SwaggerSchema returns an empty swagger.ApiDeclaration for testing
func (c *Fake) SwaggerSchema(version unversioned.GroupVersion) (*swagger.ApiDeclaration, error) {
action := ActionImpl{}
action.Verb = "get"
if version == v1.SchemeGroupVersion {
action.Resource = "/swaggerapi/api/" + version.Version
} else {
action.Resource = "/swaggerapi/apis/" + version.Group + "/" + version.Version
}
c.Invokes(action, nil)
return &swagger.ApiDeclaration{}, nil
}
// NewSimpleFakeApps returns a client that will respond with the provided objects
func NewSimpleFakeApps(objects ...runtime.Object) *FakeApps {
return &FakeApps{Fake: NewSimpleFake(objects...)}
}
type FakeApps struct {
*Fake
}
func (c *FakeApps) StatefulSets(namespace string) client.StatefulSetInterface {
return &FakeStatefulSets{Fake: c, Namespace: namespace}
}
// NewSimpleFakeAuthorization returns a client that will respond with the provided objects
func NewSimpleFakeAuthorization(objects ...runtime.Object) *FakeAuthorization {
return &FakeAuthorization{Fake: NewSimpleFake(objects...)}
}
type FakeAuthorization struct {
*Fake
}
func (c *FakeAuthorization) SubjectAccessReviews() client.SubjectAccessReviewInterface {
return &FakeSubjectAccessReviews{Fake: c}
}
// NewSimpleFakeAutoscaling returns a client that will respond with the provided objects
func NewSimpleFakeAutoscaling(objects ...runtime.Object) *FakeAutoscaling {
return &FakeAutoscaling{Fake: NewSimpleFake(objects...)}
}
type FakeAutoscaling struct {
*Fake
}
func (c *FakeAutoscaling) HorizontalPodAutoscalers(namespace string) client.HorizontalPodAutoscalerInterface {
return &FakeHorizontalPodAutoscalers{Fake: c, Namespace: namespace}
}
func NewSimpleFakeAuthentication(objects ...runtime.Object) *FakeAuthentication {
return &FakeAuthentication{Fake: NewSimpleFake(objects...)}
}
type FakeAuthentication struct {
*Fake
}
func (c *FakeAuthentication) TokenReviews() client.TokenReviewInterface {
return &FakeTokenReviews{Fake: c}
}
// NewSimpleFakeBatch returns a client that will respond with the provided objects
func NewSimpleFakeBatch(objects ...runtime.Object) *FakeBatch {
return &FakeBatch{Fake: NewSimpleFake(objects...)}
}
type FakeBatch struct {
*Fake
}
func (c *FakeBatch) Jobs(namespace string) client.JobInterface {
return &FakeJobsV1{Fake: c, Namespace: namespace}
}
func (c *FakeBatch) ScheduledJobs(namespace string) client.ScheduledJobInterface {
return &FakeScheduledJobs{Fake: c, Namespace: namespace}
}
// NewSimpleFakeExp returns a client that will respond with the provided objects
func NewSimpleFakeExp(objects ...runtime.Object) *FakeExperimental {
return &FakeExperimental{Fake: NewSimpleFake(objects...)}
}
type FakeExperimental struct {
*Fake
}
func (c *FakeExperimental) DaemonSets(namespace string) client.DaemonSetInterface {
return &FakeDaemonSets{Fake: c, Namespace: namespace}
}
func (c *FakeExperimental) Deployments(namespace string) client.DeploymentInterface {
return &FakeDeployments{Fake: c, Namespace: namespace}
}
func (c *FakeExperimental) Scales(namespace string) client.ScaleInterface {
return &FakeScales{Fake: c, Namespace: namespace}
}
func (c *FakeExperimental) Jobs(namespace string) client.JobInterface {
return &FakeJobs{Fake: c, Namespace: namespace}
}
func (c *FakeExperimental) Ingress(namespace string) client.IngressInterface {
return &FakeIngress{Fake: c, Namespace: namespace}
}
func (c *FakeExperimental) ThirdPartyResources() client.ThirdPartyResourceInterface {
return &FakeThirdPartyResources{Fake: c}
}
func (c *FakeExperimental) ReplicaSets(namespace string) client.ReplicaSetInterface {
return &FakeReplicaSets{Fake: c, Namespace: namespace}
}
func (c *FakeExperimental) NetworkPolicies(namespace string) client.NetworkPolicyInterface {
return &FakeNetworkPolicies{Fake: c, Namespace: namespace}
}
func NewSimpleFakeRbac(objects ...runtime.Object) *FakeRbac {
return &FakeRbac{Fake: NewSimpleFake(objects...)}
}
type FakeRbac struct {
*Fake
}
func (c *FakeRbac) Roles(namespace string) client.RoleInterface {
return &FakeRoles{Fake: c, Namespace: namespace}
}
func (c *FakeRbac) RoleBindings(namespace string) client.RoleBindingInterface {
return &FakeRoleBindings{Fake: c, Namespace: namespace}
}
func (c *FakeRbac) ClusterRoles() client.ClusterRoleInterface {
return &FakeClusterRoles{Fake: c}
}
func (c *FakeRbac) ClusterRoleBindings() client.ClusterRoleBindingInterface {
return &FakeClusterRoleBindings{Fake: c}
}
func NewSimpleFakeStorage(objects ...runtime.Object) *FakeStorage {
return &FakeStorage{Fake: NewSimpleFake(objects...)}
}
type FakeStorage struct {
*Fake
}
func (c *FakeStorage) StorageClasses() client.StorageClassInterface {
return &FakeStorageClasses{Fake: c}
}
type FakeDiscovery struct {
*Fake
}
func (c *FakeDiscovery) ServerPreferredResources() ([]unversioned.GroupVersionResource, error) {
return nil, nil
}
func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]unversioned.GroupVersionResource, error) {
return nil, nil
}
func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) {
action := ActionImpl{
Verb: "get",
Resource: "resource",
}
c.Invokes(action, nil)
return c.Resources[groupVersion], nil
}
func (c *FakeDiscovery) ServerResources() (map[string]*unversioned.APIResourceList, error) {
action := ActionImpl{
Verb: "get",
Resource: "resource",
}
c.Invokes(action, nil)
return c.Resources, nil
}
func (c *FakeDiscovery) ServerGroups() (*unversioned.APIGroupList, error) {
return nil, nil
}
func (c *FakeDiscovery) ServerVersion() (*version.Info, error) {
action := ActionImpl{}
action.Verb = "get"
action.Resource = "version"
c.Invokes(action, nil)
versionInfo := version.Get()
return &versionInfo, nil
}
func (c *FakeDiscovery) RESTClient() restclient.Interface {
return nil
}

View File

@ -57,9 +57,9 @@ const (
// An annotation on the Service denoting if the endpoints controller should // An annotation on the Service denoting if the endpoints controller should
// go ahead and create endpoints for unready pods. This annotation is // go ahead and create endpoints for unready pods. This annotation is
// currently only used by PetSets, where we need the pet to be DNS // currently only used by StatefulSets, where we need the pod to be DNS
// resolvable during initialization. In this situation we create a headless // resolvable during initialization. In this situation we create a headless
// service just for the PetSet, and clients shouldn't be using this Service // service just for the StatefulSet, and clients shouldn't be using this Service
// for anything so unready endpoints don't matter. // for anything so unready endpoints don't matter.
TolerateUnreadyEndpointsAnnotation = "service.alpha.kubernetes.io/tolerate-unready-endpoints" TolerateUnreadyEndpointsAnnotation = "service.alpha.kubernetes.io/tolerate-unready-endpoints"
) )

View File

@ -51,7 +51,7 @@ func newPVC(name string) api.PersistentVolumeClaim {
} }
} }
func newPetSetWithVolumes(replicas int, name string, petMounts []api.VolumeMount, podMounts []api.VolumeMount) *apps.PetSet { func newStatefulSetWithVolumes(replicas int, name string, petMounts []api.VolumeMount, podMounts []api.VolumeMount) *apps.StatefulSet {
mounts := append(petMounts, podMounts...) mounts := append(petMounts, podMounts...)
claims := []api.PersistentVolumeClaim{} claims := []api.PersistentVolumeClaim{}
for _, m := range petMounts { for _, m := range petMounts {
@ -70,9 +70,9 @@ func newPetSetWithVolumes(replicas int, name string, petMounts []api.VolumeMount
}) })
} }
return &apps.PetSet{ return &apps.StatefulSet{
TypeMeta: unversioned.TypeMeta{ TypeMeta: unversioned.TypeMeta{
Kind: "PetSet", Kind: "StatefulSet",
APIVersion: "apps/v1beta1", APIVersion: "apps/v1beta1",
}, },
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
@ -80,7 +80,7 @@ func newPetSetWithVolumes(replicas int, name string, petMounts []api.VolumeMount
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
UID: types.UID("test"), UID: types.UID("test"),
}, },
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{ Selector: &unversioned.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"}, MatchLabels: map[string]string{"foo": "bar"},
}, },
@ -110,7 +110,7 @@ func runningPod(ns, name string) *api.Pod {
return p return p
} }
func newPodList(ps *apps.PetSet, num int) []*api.Pod { func newPodList(ps *apps.StatefulSet, num int) []*api.Pod {
// knownPods are pods in the system // knownPods are pods in the system
knownPods := []*api.Pod{} knownPods := []*api.Pod{}
for i := 0; i < num; i++ { for i := 0; i < num; i++ {
@ -120,14 +120,14 @@ func newPodList(ps *apps.PetSet, num int) []*api.Pod {
return knownPods return knownPods
} }
func newPetSet(replicas int) *apps.PetSet { func newStatefulSet(replicas int) *apps.StatefulSet {
petMounts := []api.VolumeMount{ petMounts := []api.VolumeMount{
{Name: "datadir", MountPath: "/tmp/zookeeper"}, {Name: "datadir", MountPath: "/tmp/zookeeper"},
} }
podMounts := []api.VolumeMount{ podMounts := []api.VolumeMount{
{Name: "home", MountPath: "/home"}, {Name: "home", MountPath: "/home"},
} }
return newPetSetWithVolumes(replicas, "foo", petMounts, podMounts) return newStatefulSetWithVolumes(replicas, "foo", petMounts, podMounts)
} }
func checkPodForMount(pod *api.Pod, mountName string) error { func checkPodForMount(pod *api.Pod, mountName string) error {
@ -168,14 +168,14 @@ func (f *fakePetClient) Delete(p *pcb) error {
for i, pet := range f.pets { for i, pet := range f.pets {
if p.pod.Name == pet.pod.Name { if p.pod.Name == pet.pod.Name {
found = true found = true
f.recorder.Eventf(pet.parent, api.EventTypeNormal, "SuccessfulDelete", "pet: %v", pet.pod.Name) f.recorder.Eventf(pet.parent, api.EventTypeNormal, "SuccessfulDelete", "pod: %v", pet.pod.Name)
continue continue
} }
pets = append(pets, f.pets[i]) pets = append(pets, f.pets[i])
} }
if !found { if !found {
// TODO: Return proper not found error // TODO: Return proper not found error
return fmt.Errorf("Delete failed: pet %v doesn't exist", p.pod.Name) return fmt.Errorf("Delete failed: pod %v doesn't exist", p.pod.Name)
} }
f.pets = pets f.pets = pets
f.petsDeleted++ f.petsDeleted++
@ -196,10 +196,10 @@ func (f *fakePetClient) Get(p *pcb) (*pcb, bool, error) {
func (f *fakePetClient) Create(p *pcb) error { func (f *fakePetClient) Create(p *pcb) error {
for _, pet := range f.pets { for _, pet := range f.pets {
if p.pod.Name == pet.pod.Name { if p.pod.Name == pet.pod.Name {
return fmt.Errorf("Create failed: pet %v already exists", p.pod.Name) return fmt.Errorf("Create failed: pod %v already exists", p.pod.Name)
} }
} }
f.recorder.Eventf(p.parent, api.EventTypeNormal, "SuccessfulCreate", "pet: %v", p.pod.Name) f.recorder.Eventf(p.parent, api.EventTypeNormal, "SuccessfulCreate", "pod: %v", p.pod.Name)
f.pets = append(f.pets, p) f.pets = append(f.pets, p)
f.petsCreated++ f.petsCreated++
return nil return nil
@ -220,7 +220,7 @@ func (f *fakePetClient) Update(expected, wanted *pcb) error {
} }
f.pets = pets f.pets = pets
if !found { if !found {
return fmt.Errorf("Cannot update pet %v not found", wanted.pod.Name) return fmt.Errorf("Cannot update pod %v not found", wanted.pod.Name)
} }
// TODO: Delete pvcs/volumes that are in wanted but not in expected. // TODO: Delete pvcs/volumes that are in wanted but not in expected.
return nil return nil
@ -252,7 +252,7 @@ func (f *fakePetClient) setHealthy(index int) error {
return fmt.Errorf("Index out of range, len %v index %v", len(f.pets), index) return fmt.Errorf("Index out of range, len %v index %v", len(f.pets), index)
} }
f.pets[index].pod.Status.Phase = api.PodRunning f.pets[index].pod.Status.Phase = api.PodRunning
f.pets[index].pod.Annotations[PetSetInitAnnotation] = "true" f.pets[index].pod.Annotations[StatefulSetInitAnnotation] = "true"
f.pets[index].pod.Status.Conditions = []api.PodCondition{ f.pets[index].pod.Status.Conditions = []api.PodCondition{
{Type: api.PodReady, Status: api.ConditionTrue}, {Type: api.PodReady, Status: api.ConditionTrue},
} }

View File

@ -30,7 +30,7 @@ import (
) )
// identityMapper is an interface for assigning identities to a pet. // identityMapper is an interface for assigning identities to a pet.
// All existing identity mappers just append "-(index)" to the petset name to // All existing identity mappers just append "-(index)" to the statefulset name to
// generate a unique identity. This is used in claims/DNS/hostname/petname // generate a unique identity. This is used in claims/DNS/hostname/petname
// etc. There's a more elegant way to achieve this mapping, but we're // etc. There's a more elegant way to achieve this mapping, but we're
// taking the simplest route till we have data on whether users will need // taking the simplest route till we have data on whether users will need
@ -39,15 +39,15 @@ import (
// your pet a unique identity. You must run them all. Order doesn't matter. // your pet a unique identity. You must run them all. Order doesn't matter.
type identityMapper interface { type identityMapper interface {
// SetIdentity takes an id and assigns the given pet an identity based // SetIdentity takes an id and assigns the given pet an identity based
// on the pet set spec. The is must be unique amongst members of the // on the stateful set spec. The is must be unique amongst members of the
// pet set. // stateful set.
SetIdentity(id string, pet *api.Pod) SetIdentity(id string, pet *api.Pod)
// Identity returns the identity of the pet. // Identity returns the identity of the pet.
Identity(pod *api.Pod) string Identity(pod *api.Pod) string
} }
func newIdentityMappers(ps *apps.PetSet) []identityMapper { func newIdentityMappers(ps *apps.StatefulSet) []identityMapper {
return []identityMapper{ return []identityMapper{
&NameIdentityMapper{ps}, &NameIdentityMapper{ps},
&NetworkIdentityMapper{ps}, &NetworkIdentityMapper{ps},
@ -57,7 +57,7 @@ func newIdentityMappers(ps *apps.PetSet) []identityMapper {
// NetworkIdentityMapper assigns network identity to pets. // NetworkIdentityMapper assigns network identity to pets.
type NetworkIdentityMapper struct { type NetworkIdentityMapper struct {
ps *apps.PetSet ps *apps.StatefulSet
} }
// SetIdentity sets network identity on the pet. // SetIdentity sets network identity on the pet.
@ -81,7 +81,7 @@ func (n *NetworkIdentityMapper) String(pet *api.Pod) string {
// VolumeIdentityMapper assigns storage identity to pets. // VolumeIdentityMapper assigns storage identity to pets.
type VolumeIdentityMapper struct { type VolumeIdentityMapper struct {
ps *apps.PetSet ps *apps.StatefulSet
} }
// SetIdentity sets storge identity on the pet. // SetIdentity sets storge identity on the pet.
@ -90,16 +90,16 @@ func (v *VolumeIdentityMapper) SetIdentity(id string, pet *api.Pod) {
petClaims := v.GetClaims(id) petClaims := v.GetClaims(id)
// These volumes will all go down with the pod. If a name matches one of // These volumes will all go down with the pod. If a name matches one of
// the claims in the pet set, it gets clobbered. // the claims in the stateful set, it gets clobbered.
podVolumes := map[string]api.Volume{} podVolumes := map[string]api.Volume{}
for _, podVol := range pet.Spec.Volumes { for _, podVol := range pet.Spec.Volumes {
podVolumes[podVol.Name] = podVol podVolumes[podVol.Name] = podVol
} }
// Insert claims for the idempotent petSet volumes // Insert claims for the idempotent statefulset volumes
for name, claim := range petClaims { for name, claim := range petClaims {
// Volumes on a pet for which there are no associated claims on the // Volumes on a pet for which there are no associated claims on the
// petset are pod local, and die with the pod. // statefulset are pod local, and die with the pod.
podVol, ok := podVolumes[name] podVol, ok := podVolumes[name]
if ok { if ok {
// TODO: Validate and reject this. // TODO: Validate and reject this.
@ -143,7 +143,7 @@ func (v *VolumeIdentityMapper) String(pet *api.Pod) string {
} }
for _, podVol := range pet.Spec.Volumes { for _, podVol := range pet.Spec.Volumes {
// Volumes on a pet for which there are no associated claims on the // Volumes on a pet for which there are no associated claims on the
// petset are pod local, and die with the pod. // statefulset are pod local, and die with the pod.
if !petVols.Has(podVol.Name) { if !petVols.Has(podVol.Name) {
continue continue
} }
@ -159,7 +159,7 @@ func (v *VolumeIdentityMapper) String(pet *api.Pod) string {
} }
// GetClaims returns the volume claims associated with the given id. // GetClaims returns the volume claims associated with the given id.
// The claims belong to the petset. The id should be unique within a petset. // The claims belong to the statefulset. The id should be unique within a statefulset.
func (v *VolumeIdentityMapper) GetClaims(id string) map[string]api.PersistentVolumeClaim { func (v *VolumeIdentityMapper) GetClaims(id string) map[string]api.PersistentVolumeClaim {
petClaims := map[string]api.PersistentVolumeClaim{} petClaims := map[string]api.PersistentVolumeClaim{}
for _, pvc := range v.ps.Spec.VolumeClaimTemplates { for _, pvc := range v.ps.Spec.VolumeClaimTemplates {
@ -192,7 +192,7 @@ func (v *VolumeIdentityMapper) GetClaimsForPet(pet *api.Pod) []api.PersistentVol
// NameIdentityMapper assigns names to pets. // NameIdentityMapper assigns names to pets.
// It also puts the pet in the same namespace as the parent. // It also puts the pet in the same namespace as the parent.
type NameIdentityMapper struct { type NameIdentityMapper struct {
ps *apps.PetSet ps *apps.StatefulSet
} }
// SetIdentity sets the pet namespace and name. // SetIdentity sets the pet namespace and name.
@ -214,7 +214,7 @@ func (n *NameIdentityMapper) String(pet *api.Pod) string {
// identityHash computes a hash of the pet by running all the above identity // identityHash computes a hash of the pet by running all the above identity
// mappers. // mappers.
func identityHash(ps *apps.PetSet, pet *api.Pod) string { func identityHash(ps *apps.StatefulSet, pet *api.Pod) string {
id := "" id := ""
for _, idMapper := range newIdentityMappers(ps) { for _, idMapper := range newIdentityMappers(ps) {
id += idMapper.Identity(pet) id += idMapper.Identity(pet)

View File

@ -29,7 +29,7 @@ import (
func TestPetIDName(t *testing.T) { func TestPetIDName(t *testing.T) {
replicas := 3 replicas := 3
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
for i := 0; i < replicas; i++ { for i := 0; i < replicas; i++ {
petName := fmt.Sprintf("%v-%d", ps.Name, i) petName := fmt.Sprintf("%v-%d", ps.Name, i)
pcb, err := newPCB(fmt.Sprintf("%d", i), ps) pcb, err := newPCB(fmt.Sprintf("%d", i), ps)
@ -45,7 +45,7 @@ func TestPetIDName(t *testing.T) {
func TestPetIDDNS(t *testing.T) { func TestPetIDDNS(t *testing.T) {
replicas := 3 replicas := 3
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
for i := 0; i < replicas; i++ { for i := 0; i < replicas; i++ {
petName := fmt.Sprintf("%v-%d", ps.Name, i) petName := fmt.Sprintf("%v-%d", ps.Name, i)
petSubdomain := ps.Spec.ServiceName petSubdomain := ps.Spec.ServiceName
@ -65,7 +65,7 @@ func TestPetIDDNS(t *testing.T) {
} }
func TestPetIDVolume(t *testing.T) { func TestPetIDVolume(t *testing.T) {
replicas := 3 replicas := 3
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
for i := 0; i < replicas; i++ { for i := 0; i < replicas; i++ {
pcb, err := newPCB(fmt.Sprintf("%d", i), ps) pcb, err := newPCB(fmt.Sprintf("%d", i), ps)
if err != nil { if err != nil {
@ -99,7 +99,7 @@ func TestPetIDVolume(t *testing.T) {
func TestPetIDVolumeClaims(t *testing.T) { func TestPetIDVolumeClaims(t *testing.T) {
replicas := 3 replicas := 3
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
for i := 0; i < replicas; i++ { for i := 0; i < replicas; i++ {
pcb, err := newPCB(fmt.Sprintf("%v", i), ps) pcb, err := newPCB(fmt.Sprintf("%v", i), ps)
if err != nil { if err != nil {
@ -116,7 +116,7 @@ func TestPetIDVolumeClaims(t *testing.T) {
func TestPetIDCrossAssignment(t *testing.T) { func TestPetIDCrossAssignment(t *testing.T) {
replicas := 3 replicas := 3
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
nameMapper := &NameIdentityMapper{ps} nameMapper := &NameIdentityMapper{ps}
volumeMapper := &VolumeIdentityMapper{ps} volumeMapper := &VolumeIdentityMapper{ps}
@ -144,7 +144,7 @@ func TestPetIDCrossAssignment(t *testing.T) {
func TestPetIDReset(t *testing.T) { func TestPetIDReset(t *testing.T) {
replicas := 2 replicas := 2
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
firstPCB, err := newPCB("1", ps) firstPCB, err := newPCB("1", ps)
secondPCB, err := newPCB("2", ps) secondPCB, err := newPCB("2", ps)
if identityHash(ps, firstPCB.pod) == identityHash(ps, secondPCB.pod) { if identityHash(ps, firstPCB.pod) == identityHash(ps, secondPCB.pod) {

View File

@ -27,7 +27,7 @@ import (
) )
// newPCB generates a new PCB using the id string as a unique qualifier // newPCB generates a new PCB using the id string as a unique qualifier
func newPCB(id string, ps *apps.PetSet) (*pcb, error) { func newPCB(id string, ps *apps.StatefulSet) (*pcb, error) {
petPod, err := controller.GetPodFromTemplate(&ps.Spec.Template, ps, nil) petPod, err := controller.GetPodFromTemplate(&ps.Spec.Template, ps, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -87,7 +87,7 @@ func (pt *petQueue) empty() bool {
} }
// NewPetQueue returns a queue for tracking pets // NewPetQueue returns a queue for tracking pets
func NewPetQueue(ps *apps.PetSet, podList []*api.Pod) *petQueue { func NewPetQueue(ps *apps.StatefulSet, podList []*api.Pod) *petQueue {
pt := petQueue{pets: []*pcb{}, idMapper: &NameIdentityMapper{ps}} pt := petQueue{pets: []*pcb{}, idMapper: &NameIdentityMapper{ps}}
// Seed the queue with existing pets. Assume all pets are scheduled for // Seed the queue with existing pets. Assume all pets are scheduled for
// deletion, enqueuing a pet will "undelete" it. We always want to delete // deletion, enqueuing a pet will "undelete" it. We always want to delete
@ -102,10 +102,10 @@ func NewPetQueue(ps *apps.PetSet, podList []*api.Pod) *petQueue {
return &pt return &pt
} }
// petsetIterator implements a simple iterator over pets in the given petset. // statefulsetIterator implements a simple iterator over pets in the given statefulset.
type petSetIterator struct { type statefulSetIterator struct {
// ps is the petset for this iterator. // ps is the statefulset for this iterator.
ps *apps.PetSet ps *apps.StatefulSet
// queue contains the elements to iterate over. // queue contains the elements to iterate over.
queue *petQueue queue *petQueue
// errs is a list because we always want the iterator to drain. // errs is a list because we always want the iterator to drain.
@ -115,7 +115,7 @@ type petSetIterator struct {
} }
// Next returns true for as long as there are elements in the underlying queue. // Next returns true for as long as there are elements in the underlying queue.
func (pi *petSetIterator) Next() bool { func (pi *statefulSetIterator) Next() bool {
var pet *pcb var pet *pcb
var err error var err error
if pi.petCount < pi.ps.Spec.Replicas { if pi.petCount < pi.ps.Spec.Replicas {
@ -133,14 +133,14 @@ func (pi *petSetIterator) Next() bool {
} }
// Value dequeues an element from the queue. // Value dequeues an element from the queue.
func (pi *petSetIterator) Value() *pcb { func (pi *statefulSetIterator) Value() *pcb {
return pi.queue.dequeue() return pi.queue.dequeue()
} }
// NewPetSetIterator returns a new iterator. All pods in the given podList // NewStatefulSetIterator returns a new iterator. All pods in the given podList
// are used to seed the queue of the iterator. // are used to seed the queue of the iterator.
func NewPetSetIterator(ps *apps.PetSet, podList []*api.Pod) *petSetIterator { func NewStatefulSetIterator(ps *apps.StatefulSet, podList []*api.Pod) *statefulSetIterator {
pi := &petSetIterator{ pi := &statefulSetIterator{
ps: ps, ps: ps,
queue: NewPetQueue(ps, podList), queue: NewPetQueue(ps, podList),
errs: []error{}, errs: []error{},

View File

@ -27,7 +27,7 @@ import (
func TestPetQueueCreates(t *testing.T) { func TestPetQueueCreates(t *testing.T) {
replicas := 3 replicas := 3
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
q := NewPetQueue(ps, []*api.Pod{}) q := NewPetQueue(ps, []*api.Pod{})
for i := 0; i < replicas; i++ { for i := 0; i < replicas; i++ {
pet, _ := newPCB(fmt.Sprintf("%v", i), ps) pet, _ := newPCB(fmt.Sprintf("%v", i), ps)
@ -38,13 +38,13 @@ func TestPetQueueCreates(t *testing.T) {
} }
} }
if q.dequeue() != nil { if q.dequeue() != nil {
t.Errorf("Expected no pets") t.Errorf("Expected no pods")
} }
} }
func TestPetQueueScaleDown(t *testing.T) { func TestPetQueueScaleDown(t *testing.T) {
replicas := 1 replicas := 1
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
// knownPods are the pods in the system // knownPods are the pods in the system
knownPods := newPodList(ps, 3) knownPods := newPodList(ps, 3)
@ -74,13 +74,13 @@ func TestPetQueueScaleDown(t *testing.T) {
} }
} }
if q.dequeue() != nil { if q.dequeue() != nil {
t.Errorf("Expected no pets") t.Errorf("Expected no pods")
} }
} }
func TestPetQueueScaleUp(t *testing.T) { func TestPetQueueScaleUp(t *testing.T) {
replicas := 5 replicas := 5
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
// knownPods are pods in the system // knownPods are pods in the system
knownPods := newPodList(ps, 2) knownPods := newPodList(ps, 2)
@ -94,14 +94,14 @@ func TestPetQueueScaleUp(t *testing.T) {
pet := q.dequeue() pet := q.dequeue()
expectedName := fmt.Sprintf("%v-%d", ps.Name, i) expectedName := fmt.Sprintf("%v-%d", ps.Name, i)
if pet.event != syncPet || pet.pod.Name != expectedName { if pet.event != syncPet || pet.pod.Name != expectedName {
t.Errorf("Unexpected pet %+v, expected %v", pet.pod.Name, expectedName) t.Errorf("Unexpected pod %+v, expected %v", pet.pod.Name, expectedName)
} }
} }
} }
func TestPetSetIteratorRelist(t *testing.T) { func TestStatefulSetIteratorRelist(t *testing.T) {
replicas := 5 replicas := 5
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
// knownPods are pods in the system // knownPods are pods in the system
knownPods := newPodList(ps, 5) knownPods := newPodList(ps, 5)
@ -109,7 +109,7 @@ func TestPetSetIteratorRelist(t *testing.T) {
knownPods[i].Spec.NodeName = fmt.Sprintf("foo-node-%v", i) knownPods[i].Spec.NodeName = fmt.Sprintf("foo-node-%v", i)
knownPods[i].Status.Phase = api.PodRunning knownPods[i].Status.Phase = api.PodRunning
} }
pi := NewPetSetIterator(ps, knownPods) pi := NewStatefulSetIterator(ps, knownPods)
// A simple resync should not change identity of pods in the system // A simple resync should not change identity of pods in the system
i := 0 i := 0
@ -124,12 +124,12 @@ func TestPetSetIteratorRelist(t *testing.T) {
i++ i++
} }
if i != 5 { if i != 5 {
t.Errorf("Unexpected iterations %v, this probably means too many/few pets", i) t.Errorf("Unexpected iterations %v, this probably means too many/few pods", i)
} }
// Scale to 0 should delete all pods in system // Scale to 0 should delete all pods in system
ps.Spec.Replicas = 0 ps.Spec.Replicas = 0
pi = NewPetSetIterator(ps, knownPods) pi = NewStatefulSetIterator(ps, knownPods)
i = 0 i = 0
for pi.Next() { for pi.Next() {
p := pi.Value() p := pi.Value()
@ -139,11 +139,11 @@ func TestPetSetIteratorRelist(t *testing.T) {
i++ i++
} }
if i != 5 { if i != 5 {
t.Errorf("Unexpected iterations %v, this probably means too many/few pets", i) t.Errorf("Unexpected iterations %v, this probably means too many/few pods", i)
} }
// Relist with 0 replicas should no-op // Relist with 0 replicas should no-op
pi = NewPetSetIterator(ps, []*api.Pod{}) pi = NewStatefulSetIterator(ps, []*api.Pod{})
if pi.Next() != false { if pi.Next() != false {
t.Errorf("Unexpected iteration without any replicas or pods in system") t.Errorf("Unexpected iteration without any replicas or pods in system")
} }

View File

@ -41,10 +41,10 @@ const (
// updateRetries is the number of Get/Update cycles we perform when an // updateRetries is the number of Get/Update cycles we perform when an
// update fails. // update fails.
updateRetries = 3 updateRetries = 3
// PetSetInitAnnotation is an annotation which when set, indicates that the // StatefulSetInitAnnotation is an annotation which when set, indicates that the
// pet has finished initializing itself. // pet has finished initializing itself.
// TODO: Replace this with init container status. // TODO: Replace this with init container status.
PetSetInitAnnotation = "pod.alpha.kubernetes.io/initialized" StatefulSetInitAnnotation = "pod.alpha.kubernetes.io/initialized"
) )
// pcb is the control block used to transmit all updates about a single pet. // pcb is the control block used to transmit all updates about a single pet.
@ -59,8 +59,8 @@ type pcb struct {
event petLifeCycleEvent event petLifeCycleEvent
// id is the identity index of this pet. // id is the identity index of this pet.
id string id string
// parent is a pointer to the parent petset. // parent is a pointer to the parent statefulset.
parent *apps.PetSet parent *apps.StatefulSet
} }
// pvcClient is a client for managing persistent volume claims. // pvcClient is a client for managing persistent volume claims.
@ -113,12 +113,12 @@ func (p *petSyncer) Sync(pet *pcb) error {
} }
} else if exists { } else if exists {
if !p.isHealthy(realPet.pod) { if !p.isHealthy(realPet.pod) {
glog.Infof("PetSet %v waiting on unhealthy pet %v", pet.parent.Name, realPet.pod.Name) glog.Infof("StatefulSet %v waiting on unhealthy pet %v", pet.parent.Name, realPet.pod.Name)
} }
return p.Update(realPet, pet) return p.Update(realPet, pet)
} }
if p.blockingPet != nil { if p.blockingPet != nil {
message := errUnhealthyPet(fmt.Sprintf("Create of %v in PetSet %v blocked by unhealthy pet %v", pet.pod.Name, pet.parent.Name, p.blockingPet.pod.Name)) message := errUnhealthyPet(fmt.Sprintf("Create of %v in StatefulSet %v blocked by unhealthy pet %v", pet.pod.Name, pet.parent.Name, p.blockingPet.pod.Name))
glog.Info(message) glog.Info(message)
return message return message
} }
@ -135,7 +135,7 @@ func (p *petSyncer) Sync(pet *pcb) error {
return nil return nil
} }
// Delete deletes the given pet, if no other pet in the petset is blocking a // Delete deletes the given pet, if no other pet in the statefulset is blocking a
// scale event. // scale event.
func (p *petSyncer) Delete(pet *pcb) error { func (p *petSyncer) Delete(pet *pcb) error {
if pet == nil { if pet == nil {
@ -149,17 +149,17 @@ func (p *petSyncer) Delete(pet *pcb) error {
return nil return nil
} }
if p.blockingPet != nil { if p.blockingPet != nil {
glog.Infof("Delete of %v in PetSet %v blocked by unhealthy pet %v", realPet.pod.Name, pet.parent.Name, p.blockingPet.pod.Name) glog.Infof("Delete of %v in StatefulSet %v blocked by unhealthy pet %v", realPet.pod.Name, pet.parent.Name, p.blockingPet.pod.Name)
return nil return nil
} }
// This is counted as a delete, even if it fails. // This is counted as a delete, even if it fails.
// The returned error will force a requeue. // The returned error will force a requeue.
p.blockingPet = realPet p.blockingPet = realPet
if !p.isDying(realPet.pod) { if !p.isDying(realPet.pod) {
glog.Infof("PetSet %v deleting pet %v", pet.parent.Name, pet.pod.Name) glog.Infof("StatefulSet %v deleting pet %v", pet.parent.Name, pet.pod.Name)
return p.petClient.Delete(pet) return p.petClient.Delete(pet)
} }
glog.Infof("PetSet %v waiting on pet %v to die in %v", pet.parent.Name, realPet.pod.Name, realPet.pod.DeletionTimestamp) glog.Infof("StatefulSet %v waiting on pet %v to die in %v", pet.parent.Name, realPet.pod.Name, realPet.pod.DeletionTimestamp)
return nil return nil
} }
@ -173,7 +173,7 @@ type petClient interface {
Update(*pcb, *pcb) error Update(*pcb, *pcb) error
} }
// apiServerPetClient is a petset aware Kubernetes client. // apiServerPetClient is a statefulset aware Kubernetes client.
type apiServerPetClient struct { type apiServerPetClient struct {
c internalclientset.Interface c internalclientset.Interface
recorder record.EventRecorder recorder record.EventRecorder
@ -223,7 +223,7 @@ func (p *apiServerPetClient) Update(pet *pcb, expectedPet *pcb) (updateErr error
if err != nil || !needsUpdate { if err != nil || !needsUpdate {
return err return err
} }
glog.Infof("Resetting pet %v/%v to match PetSet %v spec", pet.pod.Namespace, pet.pod.Name, pet.parent.Name) glog.Infof("Resetting pet %v/%v to match StatefulSet %v spec", pet.pod.Namespace, pet.pod.Name, pet.parent.Name)
_, updateErr = pc.Update(&updatePod) _, updateErr = pc.Update(&updatePod)
if updateErr == nil || i >= updateRetries { if updateErr == nil || i >= updateRetries {
return updateErr return updateErr
@ -303,9 +303,9 @@ func (d *defaultPetHealthChecker) isHealthy(pod *api.Pod) bool {
if pod == nil || pod.Status.Phase != api.PodRunning { if pod == nil || pod.Status.Phase != api.PodRunning {
return false return false
} }
initialized, ok := pod.Annotations[PetSetInitAnnotation] initialized, ok := pod.Annotations[StatefulSetInitAnnotation]
if !ok { if !ok {
glog.Infof("PetSet pod %v in %v, waiting on annotation %v", api.PodRunning, pod.Name, PetSetInitAnnotation) glog.Infof("StatefulSet pod %v in %v, waiting on annotation %v", api.PodRunning, pod.Name, StatefulSetInitAnnotation)
return false return false
} }
b, err := strconv.ParseBool(initialized) b, err := strconv.ParseBool(initialized)

View File

@ -46,12 +46,12 @@ const (
PodStoreSyncedPollPeriod = 100 * time.Millisecond PodStoreSyncedPollPeriod = 100 * time.Millisecond
// number of retries for a status update. // number of retries for a status update.
statusUpdateRetries = 2 statusUpdateRetries = 2
// period to relist petsets and verify pets // period to relist statefulsets and verify pets
petSetResyncPeriod = 30 * time.Second statefulSetResyncPeriod = 30 * time.Second
) )
// PetSetController controls petsets. // StatefulSetController controls statefulsets.
type PetSetController struct { type StatefulSetController struct {
kubeClient internalclientset.Interface kubeClient internalclientset.Interface
// newSyncer returns an interface capable of syncing a single pet. // newSyncer returns an interface capable of syncing a single pet.
@ -66,9 +66,9 @@ type PetSetController struct {
// Watches changes to all pods. // Watches changes to all pods.
podController cache.ControllerInterface podController cache.ControllerInterface
// A store of PetSets, populated by the psController. // A store of StatefulSets, populated by the psController.
psStore cache.StoreToPetSetLister psStore cache.StoreToStatefulSetLister
// Watches changes to all PetSets. // Watches changes to all StatefulSets.
psController *cache.Controller psController *cache.Controller
// A store of the 1 unhealthy pet blocking progress for a given ps // A store of the 1 unhealthy pet blocking progress for a given ps
@ -77,34 +77,34 @@ type PetSetController struct {
// Controllers that need to be synced. // Controllers that need to be synced.
queue workqueue.RateLimitingInterface queue workqueue.RateLimitingInterface
// syncHandler handles sync events for petsets. // syncHandler handles sync events for statefulsets.
// Abstracted as a func to allow injection for testing. // Abstracted as a func to allow injection for testing.
syncHandler func(psKey string) error syncHandler func(psKey string) error
} }
// NewPetSetController creates a new petset controller. // NewStatefulSetController creates a new statefulset controller.
func NewPetSetController(podInformer cache.SharedIndexInformer, kubeClient internalclientset.Interface, resyncPeriod time.Duration) *PetSetController { func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient internalclientset.Interface, resyncPeriod time.Duration) *StatefulSetController {
eventBroadcaster := record.NewBroadcaster() eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartLogging(glog.Infof)
eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: kubeClient.Core().Events("")}) eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: kubeClient.Core().Events("")})
recorder := eventBroadcaster.NewRecorder(api.EventSource{Component: "petset"}) recorder := eventBroadcaster.NewRecorder(api.EventSource{Component: "statefulset"})
pc := &apiServerPetClient{kubeClient, recorder, &defaultPetHealthChecker{}} pc := &apiServerPetClient{kubeClient, recorder, &defaultPetHealthChecker{}}
psc := &PetSetController{ psc := &StatefulSetController{
kubeClient: kubeClient, kubeClient: kubeClient,
blockingPetStore: newUnHealthyPetTracker(pc), blockingPetStore: newUnHealthyPetTracker(pc),
newSyncer: func(blockingPet *pcb) *petSyncer { newSyncer: func(blockingPet *pcb) *petSyncer {
return &petSyncer{pc, blockingPet} return &petSyncer{pc, blockingPet}
}, },
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "petset"), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "statefulset"),
} }
podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
// lookup the petset and enqueue // lookup the statefulset and enqueue
AddFunc: psc.addPod, AddFunc: psc.addPod,
// lookup current and old petset if labels changed // lookup current and old statefulset if labels changed
UpdateFunc: psc.updatePod, UpdateFunc: psc.updatePod,
// lookup petset accounting for deletion tombstones // lookup statefulset accounting for deletion tombstones
DeleteFunc: psc.deletePod, DeleteFunc: psc.deletePod,
}) })
psc.podStore.Indexer = podInformer.GetIndexer() psc.podStore.Indexer = podInformer.GetIndexer()
@ -113,25 +113,25 @@ func NewPetSetController(podInformer cache.SharedIndexInformer, kubeClient inter
psc.psStore.Store, psc.psController = cache.NewInformer( psc.psStore.Store, psc.psController = cache.NewInformer(
&cache.ListWatch{ &cache.ListWatch{
ListFunc: func(options api.ListOptions) (runtime.Object, error) { ListFunc: func(options api.ListOptions) (runtime.Object, error) {
return psc.kubeClient.Apps().PetSets(api.NamespaceAll).List(options) return psc.kubeClient.Apps().StatefulSets(api.NamespaceAll).List(options)
}, },
WatchFunc: func(options api.ListOptions) (watch.Interface, error) { WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
return psc.kubeClient.Apps().PetSets(api.NamespaceAll).Watch(options) return psc.kubeClient.Apps().StatefulSets(api.NamespaceAll).Watch(options)
}, },
}, },
&apps.PetSet{}, &apps.StatefulSet{},
petSetResyncPeriod, statefulSetResyncPeriod,
cache.ResourceEventHandlerFuncs{ cache.ResourceEventHandlerFuncs{
AddFunc: psc.enqueuePetSet, AddFunc: psc.enqueueStatefulSet,
UpdateFunc: func(old, cur interface{}) { UpdateFunc: func(old, cur interface{}) {
oldPS := old.(*apps.PetSet) oldPS := old.(*apps.StatefulSet)
curPS := cur.(*apps.PetSet) curPS := cur.(*apps.StatefulSet)
if oldPS.Status.Replicas != curPS.Status.Replicas { if oldPS.Status.Replicas != curPS.Status.Replicas {
glog.V(4).Infof("Observed updated replica count for PetSet: %v, %d->%d", curPS.Name, oldPS.Status.Replicas, curPS.Status.Replicas) glog.V(4).Infof("Observed updated replica count for StatefulSet: %v, %d->%d", curPS.Name, oldPS.Status.Replicas, curPS.Status.Replicas)
} }
psc.enqueuePetSet(cur) psc.enqueueStatefulSet(cur)
}, },
DeleteFunc: psc.enqueuePetSet, DeleteFunc: psc.enqueueStatefulSet,
}, },
) )
// TODO: Watch volumes // TODO: Watch volumes
@ -140,34 +140,34 @@ func NewPetSetController(podInformer cache.SharedIndexInformer, kubeClient inter
return psc return psc
} }
// Run runs the petset controller. // Run runs the statefulset controller.
func (psc *PetSetController) Run(workers int, stopCh <-chan struct{}) { func (psc *StatefulSetController) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash() defer utilruntime.HandleCrash()
glog.Infof("Starting petset controller") glog.Infof("Starting statefulset controller")
go psc.podController.Run(stopCh) go psc.podController.Run(stopCh)
go psc.psController.Run(stopCh) go psc.psController.Run(stopCh)
for i := 0; i < workers; i++ { for i := 0; i < workers; i++ {
go wait.Until(psc.worker, time.Second, stopCh) go wait.Until(psc.worker, time.Second, stopCh)
} }
<-stopCh <-stopCh
glog.Infof("Shutting down petset controller") glog.Infof("Shutting down statefulset controller")
psc.queue.ShutDown() psc.queue.ShutDown()
} }
// addPod adds the petset for the pod to the sync queue // addPod adds the statefulset for the pod to the sync queue
func (psc *PetSetController) addPod(obj interface{}) { func (psc *StatefulSetController) addPod(obj interface{}) {
pod := obj.(*api.Pod) pod := obj.(*api.Pod)
glog.V(4).Infof("Pod %s created, labels: %+v", pod.Name, pod.Labels) glog.V(4).Infof("Pod %s created, labels: %+v", pod.Name, pod.Labels)
ps := psc.getPetSetForPod(pod) ps := psc.getStatefulSetForPod(pod)
if ps == nil { if ps == nil {
return return
} }
psc.enqueuePetSet(ps) psc.enqueueStatefulSet(ps)
} }
// updatePod adds the petset for the current and old pods to the sync queue. // updatePod adds the statefulset for the current and old pods to the sync queue.
// If the labels of the pod didn't change, this method enqueues a single petset. // If the labels of the pod didn't change, this method enqueues a single statefulset.
func (psc *PetSetController) updatePod(old, cur interface{}) { func (psc *StatefulSetController) updatePod(old, cur interface{}) {
curPod := cur.(*api.Pod) curPod := cur.(*api.Pod)
oldPod := old.(*api.Pod) oldPod := old.(*api.Pod)
if curPod.ResourceVersion == oldPod.ResourceVersion { if curPod.ResourceVersion == oldPod.ResourceVersion {
@ -175,26 +175,26 @@ func (psc *PetSetController) updatePod(old, cur interface{}) {
// Two different versions of the same pod will always have different RVs. // Two different versions of the same pod will always have different RVs.
return return
} }
ps := psc.getPetSetForPod(curPod) ps := psc.getStatefulSetForPod(curPod)
if ps == nil { if ps == nil {
return return
} }
psc.enqueuePetSet(ps) psc.enqueueStatefulSet(ps)
if !reflect.DeepEqual(curPod.Labels, oldPod.Labels) { if !reflect.DeepEqual(curPod.Labels, oldPod.Labels) {
if oldPS := psc.getPetSetForPod(oldPod); oldPS != nil { if oldPS := psc.getStatefulSetForPod(oldPod); oldPS != nil {
psc.enqueuePetSet(oldPS) psc.enqueueStatefulSet(oldPS)
} }
} }
} }
// deletePod enqueues the petset for the pod accounting for deletion tombstones. // deletePod enqueues the statefulset for the pod accounting for deletion tombstones.
func (psc *PetSetController) deletePod(obj interface{}) { func (psc *StatefulSetController) deletePod(obj interface{}) {
pod, ok := obj.(*api.Pod) pod, ok := obj.(*api.Pod)
// When a delete is dropped, the relist will notice a pod in the store not // When a delete is dropped, the relist will notice a pod in the store not
// in the list, leading to the insertion of a tombstone object which contains // in the list, leading to the insertion of a tombstone object which contains
// the deleted key/value. Note that this value might be stale. If the pod // the deleted key/value. Note that this value might be stale. If the pod
// changed labels the new PetSet will not be woken up till the periodic resync. // changed labels the new StatefulSet will not be woken up till the periodic resync.
if !ok { if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown) tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok { if !ok {
@ -208,14 +208,14 @@ func (psc *PetSetController) deletePod(obj interface{}) {
} }
} }
glog.V(4).Infof("Pod %s/%s deleted through %v.", pod.Namespace, pod.Name, utilruntime.GetCaller()) glog.V(4).Infof("Pod %s/%s deleted through %v.", pod.Namespace, pod.Name, utilruntime.GetCaller())
if ps := psc.getPetSetForPod(pod); ps != nil { if ps := psc.getStatefulSetForPod(pod); ps != nil {
psc.enqueuePetSet(ps) psc.enqueueStatefulSet(ps)
} }
} }
// getPodsForPetSets returns the pods that match the selectors of the given petset. // getPodsForStatefulSets returns the pods that match the selectors of the given statefulset.
func (psc *PetSetController) getPodsForPetSet(ps *apps.PetSet) ([]*api.Pod, error) { func (psc *StatefulSetController) getPodsForStatefulSet(ps *apps.StatefulSet) ([]*api.Pod, error) {
// TODO: Do we want the petset to fight with RCs? check parent petset annoation, or name prefix? // TODO: Do we want the statefulset to fight with RCs? check parent statefulset annoation, or name prefix?
sel, err := unversioned.LabelSelectorAsSelector(ps.Spec.Selector) sel, err := unversioned.LabelSelectorAsSelector(ps.Spec.Selector)
if err != nil { if err != nil {
return []*api.Pod{}, err return []*api.Pod{}, err
@ -232,24 +232,24 @@ func (psc *PetSetController) getPodsForPetSet(ps *apps.PetSet) ([]*api.Pod, erro
return result, nil return result, nil
} }
// getPetSetForPod returns the pet set managing the given pod. // getStatefulSetForPod returns the pet set managing the given pod.
func (psc *PetSetController) getPetSetForPod(pod *api.Pod) *apps.PetSet { func (psc *StatefulSetController) getStatefulSetForPod(pod *api.Pod) *apps.StatefulSet {
ps, err := psc.psStore.GetPodPetSets(pod) ps, err := psc.psStore.GetPodStatefulSets(pod)
if err != nil { if err != nil {
glog.V(4).Infof("No PetSets found for pod %v, PetSet controller will avoid syncing", pod.Name) glog.V(4).Infof("No StatefulSets found for pod %v, StatefulSet controller will avoid syncing", pod.Name)
return nil return nil
} }
// Resolve a overlapping petset tie by creation timestamp. // Resolve a overlapping statefulset tie by creation timestamp.
// Let's hope users don't create overlapping petsets. // Let's hope users don't create overlapping statefulsets.
if len(ps) > 1 { if len(ps) > 1 {
glog.Errorf("user error! more than one PetSet is selecting pods with labels: %+v", pod.Labels) glog.Errorf("user error! more than one StatefulSet is selecting pods with labels: %+v", pod.Labels)
sort.Sort(overlappingPetSets(ps)) sort.Sort(overlappingStatefulSets(ps))
} }
return &ps[0] return &ps[0]
} }
// enqueuePetSet enqueues the given petset in the work queue. // enqueueStatefulSet enqueues the given statefulset in the work queue.
func (psc *PetSetController) enqueuePetSet(obj interface{}) { func (psc *StatefulSetController) enqueueStatefulSet(obj interface{}) {
key, err := controller.KeyFunc(obj) key, err := controller.KeyFunc(obj)
if err != nil { if err != nil {
glog.Errorf("Cound't get key for object %+v: %v", obj, err) glog.Errorf("Cound't get key for object %+v: %v", obj, err)
@ -260,7 +260,7 @@ func (psc *PetSetController) enqueuePetSet(obj interface{}) {
// worker runs a worker thread that just dequeues items, processes them, and marks them done. // worker runs a worker thread that just dequeues items, processes them, and marks them done.
// It enforces that the syncHandler is never invoked concurrently with the same key. // It enforces that the syncHandler is never invoked concurrently with the same key.
func (psc *PetSetController) worker() { func (psc *StatefulSetController) worker() {
for { for {
func() { func() {
key, quit := psc.queue.Get() key, quit := psc.queue.Get()
@ -269,7 +269,7 @@ func (psc *PetSetController) worker() {
} }
defer psc.queue.Done(key) defer psc.queue.Done(key)
if err := psc.syncHandler(key.(string)); err != nil { if err := psc.syncHandler(key.(string)); err != nil {
glog.Errorf("Error syncing PetSet %v, requeuing: %v", key.(string), err) glog.Errorf("Error syncing StatefulSet %v, requeuing: %v", key.(string), err)
psc.queue.AddRateLimited(key) psc.queue.AddRateLimited(key)
} else { } else {
psc.queue.Forget(key) psc.queue.Forget(key)
@ -278,11 +278,11 @@ func (psc *PetSetController) worker() {
} }
} }
// Sync syncs the given petset. // Sync syncs the given statefulset.
func (psc *PetSetController) Sync(key string) error { func (psc *StatefulSetController) Sync(key string) error {
startTime := time.Now() startTime := time.Now()
defer func() { defer func() {
glog.V(4).Infof("Finished syncing pet set %q (%v)", key, time.Now().Sub(startTime)) glog.V(4).Infof("Finished syncing statefulset %q (%v)", key, time.Now().Sub(startTime))
}() }()
if !psc.podStoreSynced() { if !psc.podStoreSynced() {
@ -296,40 +296,40 @@ func (psc *PetSetController) Sync(key string) error {
if err = psc.blockingPetStore.store.Delete(key); err != nil { if err = psc.blockingPetStore.store.Delete(key); err != nil {
return err return err
} }
glog.Infof("PetSet has been deleted %v", key) glog.Infof("StatefulSet has been deleted %v", key)
return nil return nil
} }
if err != nil { if err != nil {
glog.Errorf("Unable to retrieve PetSet %v from store: %v", key, err) glog.Errorf("Unable to retrieve StatefulSet %v from store: %v", key, err)
return err return err
} }
ps := *obj.(*apps.PetSet) ps := *obj.(*apps.StatefulSet)
petList, err := psc.getPodsForPetSet(&ps) petList, err := psc.getPodsForStatefulSet(&ps)
if err != nil { if err != nil {
return err return err
} }
numPets, syncErr := psc.syncPetSet(&ps, petList) numPets, syncErr := psc.syncStatefulSet(&ps, petList)
if updateErr := updatePetCount(psc.kubeClient.Apps(), ps, numPets); updateErr != nil { if updateErr := updatePetCount(psc.kubeClient.Apps(), ps, numPets); updateErr != nil {
glog.Infof("Failed to update replica count for petset %v/%v; requeuing; error: %v", ps.Namespace, ps.Name, updateErr) glog.Infof("Failed to update replica count for statefulset %v/%v; requeuing; error: %v", ps.Namespace, ps.Name, updateErr)
return errors.NewAggregate([]error{syncErr, updateErr}) return errors.NewAggregate([]error{syncErr, updateErr})
} }
return syncErr return syncErr
} }
// syncPetSet syncs a tuple of (petset, pets). // syncStatefulSet syncs a tuple of (statefulset, pets).
func (psc *PetSetController) syncPetSet(ps *apps.PetSet, pets []*api.Pod) (int, error) { func (psc *StatefulSetController) syncStatefulSet(ps *apps.StatefulSet, pets []*api.Pod) (int, error) {
glog.Infof("Syncing PetSet %v/%v with %d pets", ps.Namespace, ps.Name, len(pets)) glog.Infof("Syncing StatefulSet %v/%v with %d pods", ps.Namespace, ps.Name, len(pets))
it := NewPetSetIterator(ps, pets) it := NewStatefulSetIterator(ps, pets)
blockingPet, err := psc.blockingPetStore.Get(ps, pets) blockingPet, err := psc.blockingPetStore.Get(ps, pets)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if blockingPet != nil { if blockingPet != nil {
glog.Infof("PetSet %v blocked from scaling on pet %v", ps.Name, blockingPet.pod.Name) glog.Infof("StatefulSet %v blocked from scaling on pod %v", ps.Name, blockingPet.pod.Name)
} }
petManager := psc.newSyncer(blockingPet) petManager := psc.newSyncer(blockingPet)
numPets := 0 numPets := 0
@ -351,7 +351,7 @@ func (psc *PetSetController) syncPetSet(ps *apps.PetSet, pets []*api.Pod) (int,
switch err.(type) { switch err.(type) {
case errUnhealthyPet: case errUnhealthyPet:
// We are not passing this error up, but we don't increment numPets if we encounter it, // We are not passing this error up, but we don't increment numPets if we encounter it,
// since numPets directly translates to petset.status.replicas // since numPets directly translates to statefulset.status.replicas
continue continue
case nil: case nil:
continue continue
@ -364,7 +364,7 @@ func (psc *PetSetController) syncPetSet(ps *apps.PetSet, pets []*api.Pod) (int,
it.errs = append(it.errs, err) it.errs = append(it.errs, err)
} }
// TODO: GC pvcs. We can't delete them per pet because of grace period, and // TODO: GC pvcs. We can't delete them per pet because of grace period, and
// in fact we *don't want to* till petset is stable to guarantee that bugs // in fact we *don't want to* till statefulset is stable to guarantee that bugs
// in the controller don't corrupt user data. // in the controller don't corrupt user data.
return numPets, errors.NewAggregate(it.errs) return numPets, errors.NewAggregate(it.errs)
} }

View File

@ -32,13 +32,13 @@ import (
"k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/errors"
) )
func newFakePetSetController() (*PetSetController, *fakePetClient) { func newFakeStatefulSetController() (*StatefulSetController, *fakePetClient) {
fpc := newFakePetClient() fpc := newFakePetClient()
return &PetSetController{ return &StatefulSetController{
kubeClient: nil, kubeClient: nil,
blockingPetStore: newUnHealthyPetTracker(fpc), blockingPetStore: newUnHealthyPetTracker(fpc),
podStoreSynced: func() bool { return true }, podStoreSynced: func() bool { return true },
psStore: cache.StoreToPetSetLister{Store: cache.NewStore(controller.KeyFunc)}, psStore: cache.StoreToStatefulSetLister{Store: cache.NewStore(controller.KeyFunc)},
podStore: cache.StoreToPodLister{Indexer: cache.NewIndexer(controller.KeyFunc, cache.Indexers{})}, podStore: cache.StoreToPodLister{Indexer: cache.NewIndexer(controller.KeyFunc, cache.Indexers{})},
newSyncer: func(blockingPet *pcb) *petSyncer { newSyncer: func(blockingPet *pcb) *petSyncer {
return &petSyncer{fpc, blockingPet} return &petSyncer{fpc, blockingPet}
@ -46,7 +46,7 @@ func newFakePetSetController() (*PetSetController, *fakePetClient) {
}, fpc }, fpc
} }
func checkPets(ps *apps.PetSet, creates, deletes int, fc *fakePetClient, t *testing.T) { func checkPets(ps *apps.StatefulSet, creates, deletes int, fc *fakePetClient, t *testing.T) {
if fc.petsCreated != creates || fc.petsDeleted != deletes { if fc.petsCreated != creates || fc.petsDeleted != deletes {
t.Errorf("Found (creates: %d, deletes: %d), expected (creates: %d, deletes: %d)", fc.petsCreated, fc.petsDeleted, creates, deletes) t.Errorf("Found (creates: %d, deletes: %d), expected (creates: %d, deletes: %d)", fc.petsCreated, fc.petsDeleted, creates, deletes)
} }
@ -57,12 +57,12 @@ func checkPets(ps *apps.PetSet, creates, deletes int, fc *fakePetClient, t *test
for i := range fc.pets { for i := range fc.pets {
expectedPet, _ := newPCB(fmt.Sprintf("%v", i), ps) expectedPet, _ := newPCB(fmt.Sprintf("%v", i), ps)
if identityHash(ps, fc.pets[i].pod) != identityHash(ps, expectedPet.pod) { if identityHash(ps, fc.pets[i].pod) != identityHash(ps, expectedPet.pod) {
t.Errorf("Unexpected pet at index %d", i) t.Errorf("Unexpected pod at index %d", i)
} }
for _, pvc := range expectedPet.pvcs { for _, pvc := range expectedPet.pvcs {
gotPVC, ok := gotClaims[pvc.Name] gotPVC, ok := gotClaims[pvc.Name]
if !ok { if !ok {
t.Errorf("PVC %v not created for pet %v", pvc.Name, expectedPet.pod.Name) t.Errorf("PVC %v not created for pod %v", pvc.Name, expectedPet.pod.Name)
} }
if !reflect.DeepEqual(gotPVC.Spec, pvc.Spec) { if !reflect.DeepEqual(gotPVC.Spec, pvc.Spec) {
t.Errorf("got PVC %v differs from created pvc", pvc.Name) t.Errorf("got PVC %v differs from created pvc", pvc.Name)
@ -71,14 +71,14 @@ func checkPets(ps *apps.PetSet, creates, deletes int, fc *fakePetClient, t *test
} }
} }
func scalePetSet(t *testing.T, ps *apps.PetSet, psc *PetSetController, fc *fakePetClient, scale int) error { func scaleStatefulSet(t *testing.T, ps *apps.StatefulSet, psc *StatefulSetController, fc *fakePetClient, scale int) error {
errs := []error{} errs := []error{}
for i := 0; i < scale; i++ { for i := 0; i < scale; i++ {
pl := fc.getPodList() pl := fc.getPodList()
if len(pl) != i { if len(pl) != i {
t.Errorf("Unexpected number of pets, expected %d found %d", i, len(pl)) t.Errorf("Unexpected number of pods, expected %d found %d", i, len(pl))
} }
if _, syncErr := psc.syncPetSet(ps, pl); syncErr != nil { if _, syncErr := psc.syncStatefulSet(ps, pl); syncErr != nil {
errs = append(errs, syncErr) errs = append(errs, syncErr)
} }
fc.setHealthy(i) fc.setHealthy(i)
@ -87,35 +87,35 @@ func scalePetSet(t *testing.T, ps *apps.PetSet, psc *PetSetController, fc *fakeP
return errors.NewAggregate(errs) return errors.NewAggregate(errs)
} }
func saturatePetSet(t *testing.T, ps *apps.PetSet, psc *PetSetController, fc *fakePetClient) { func saturateStatefulSet(t *testing.T, ps *apps.StatefulSet, psc *StatefulSetController, fc *fakePetClient) {
err := scalePetSet(t, ps, psc, fc, int(ps.Spec.Replicas)) err := scaleStatefulSet(t, ps, psc, fc, int(ps.Spec.Replicas))
if err != nil { if err != nil {
t.Errorf("Error scalePetSet: %v", err) t.Errorf("Error scaleStatefulSet: %v", err)
} }
} }
func TestPetSetControllerCreates(t *testing.T) { func TestStatefulSetControllerCreates(t *testing.T) {
psc, fc := newFakePetSetController() psc, fc := newFakeStatefulSetController()
replicas := 3 replicas := 3
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
saturatePetSet(t, ps, psc, fc) saturateStatefulSet(t, ps, psc, fc)
podList := fc.getPodList() podList := fc.getPodList()
// Deleted pet gets recreated // Deleted pet gets recreated
fc.pets = fc.pets[:replicas-1] fc.pets = fc.pets[:replicas-1]
if _, err := psc.syncPetSet(ps, podList); err != nil { if _, err := psc.syncStatefulSet(ps, podList); err != nil {
t.Errorf("Error syncing PetSet: %v", err) t.Errorf("Error syncing StatefulSet: %v", err)
} }
checkPets(ps, replicas+1, 0, fc, t) checkPets(ps, replicas+1, 0, fc, t)
} }
func TestPetSetControllerDeletes(t *testing.T) { func TestStatefulSetControllerDeletes(t *testing.T) {
psc, fc := newFakePetSetController() psc, fc := newFakeStatefulSetController()
replicas := 4 replicas := 4
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
saturatePetSet(t, ps, psc, fc) saturateStatefulSet(t, ps, psc, fc)
// Drain // Drain
errs := []error{} errs := []error{}
@ -123,30 +123,30 @@ func TestPetSetControllerDeletes(t *testing.T) {
knownPods := fc.getPodList() knownPods := fc.getPodList()
for i := replicas - 1; i >= 0; i-- { for i := replicas - 1; i >= 0; i-- {
if len(fc.pets) != i+1 { if len(fc.pets) != i+1 {
t.Errorf("Unexpected number of pets, expected %d found %d", i+1, len(fc.pets)) t.Errorf("Unexpected number of pods, expected %d found %d", i+1, len(fc.pets))
} }
if _, syncErr := psc.syncPetSet(ps, knownPods); syncErr != nil { if _, syncErr := psc.syncStatefulSet(ps, knownPods); syncErr != nil {
errs = append(errs, syncErr) errs = append(errs, syncErr)
} }
} }
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("Error syncing PetSet: %v", errors.NewAggregate(errs)) t.Errorf("Error syncing StatefulSet: %v", errors.NewAggregate(errs))
} }
checkPets(ps, replicas, replicas, fc, t) checkPets(ps, replicas, replicas, fc, t)
} }
func TestPetSetControllerRespectsTermination(t *testing.T) { func TestStatefulSetControllerRespectsTermination(t *testing.T) {
psc, fc := newFakePetSetController() psc, fc := newFakeStatefulSetController()
replicas := 4 replicas := 4
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
saturatePetSet(t, ps, psc, fc) saturateStatefulSet(t, ps, psc, fc)
fc.setDeletionTimestamp(replicas - 1) fc.setDeletionTimestamp(replicas - 1)
ps.Spec.Replicas = 2 ps.Spec.Replicas = 2
_, err := psc.syncPetSet(ps, fc.getPodList()) _, err := psc.syncStatefulSet(ps, fc.getPodList())
if err != nil { if err != nil {
t.Errorf("Error syncing PetSet: %v", err) t.Errorf("Error syncing StatefulSet: %v", err)
} }
// Finding a pod with the deletion timestamp will pause all deletions. // Finding a pod with the deletion timestamp will pause all deletions.
knownPods := fc.getPodList() knownPods := fc.getPodList()
@ -154,19 +154,19 @@ func TestPetSetControllerRespectsTermination(t *testing.T) {
t.Errorf("Pods deleted prematurely before deletion timestamp expired, len %d", len(knownPods)) t.Errorf("Pods deleted prematurely before deletion timestamp expired, len %d", len(knownPods))
} }
fc.pets = fc.pets[:replicas-1] fc.pets = fc.pets[:replicas-1]
_, err = psc.syncPetSet(ps, fc.getPodList()) _, err = psc.syncStatefulSet(ps, fc.getPodList())
if err != nil { if err != nil {
t.Errorf("Error syncing PetSet: %v", err) t.Errorf("Error syncing StatefulSet: %v", err)
} }
checkPets(ps, replicas, 1, fc, t) checkPets(ps, replicas, 1, fc, t)
} }
func TestPetSetControllerRespectsOrder(t *testing.T) { func TestStatefulSetControllerRespectsOrder(t *testing.T) {
psc, fc := newFakePetSetController() psc, fc := newFakeStatefulSetController()
replicas := 4 replicas := 4
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
saturatePetSet(t, ps, psc, fc) saturateStatefulSet(t, ps, psc, fc)
errs := []error{} errs := []error{}
ps.Spec.Replicas = 0 ps.Spec.Replicas = 0
@ -179,36 +179,36 @@ func TestPetSetControllerRespectsOrder(t *testing.T) {
for i := 0; i < replicas; i++ { for i := 0; i < replicas; i++ {
if len(fc.pets) != replicas-i { if len(fc.pets) != replicas-i {
t.Errorf("Unexpected number of pets, expected %d found %d", i, len(fc.pets)) t.Errorf("Unexpected number of pods, expected %d found %d", i, len(fc.pets))
} }
if _, syncErr := psc.syncPetSet(ps, knownPods); syncErr != nil { if _, syncErr := psc.syncStatefulSet(ps, knownPods); syncErr != nil {
errs = append(errs, syncErr) errs = append(errs, syncErr)
} }
checkPets(ps, replicas, i+1, fc, t) checkPets(ps, replicas, i+1, fc, t)
} }
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("Error syncing PetSet: %v", errors.NewAggregate(errs)) t.Errorf("Error syncing StatefulSet: %v", errors.NewAggregate(errs))
} }
} }
func TestPetSetControllerBlocksScaling(t *testing.T) { func TestStatefulSetControllerBlocksScaling(t *testing.T) {
psc, fc := newFakePetSetController() psc, fc := newFakeStatefulSetController()
replicas := 5 replicas := 5
ps := newPetSet(replicas) ps := newStatefulSet(replicas)
scalePetSet(t, ps, psc, fc, 3) scaleStatefulSet(t, ps, psc, fc, 3)
// Create 4th pet, then before flipping it to healthy, kill the first pet. // Create 4th pet, then before flipping it to healthy, kill the first pet.
// There should only be 1 not-healty pet at a time. // There should only be 1 not-healty pet at a time.
pl := fc.getPodList() pl := fc.getPodList()
if _, err := psc.syncPetSet(ps, pl); err != nil { if _, err := psc.syncStatefulSet(ps, pl); err != nil {
t.Errorf("Error syncing PetSet: %v", err) t.Errorf("Error syncing StatefulSet: %v", err)
} }
deletedPod := pl[0] deletedPod := pl[0]
fc.deletePetAtIndex(0) fc.deletePetAtIndex(0)
pl = fc.getPodList() pl = fc.getPodList()
if _, err := psc.syncPetSet(ps, pl); err != nil { if _, err := psc.syncStatefulSet(ps, pl); err != nil {
t.Errorf("Error syncing PetSet: %v", err) t.Errorf("Error syncing StatefulSet: %v", err)
} }
newPodList := fc.getPodList() newPodList := fc.getPodList()
for _, p := range newPodList { for _, p := range newPodList {
@ -218,8 +218,8 @@ func TestPetSetControllerBlocksScaling(t *testing.T) {
} }
fc.setHealthy(len(newPodList) - 1) fc.setHealthy(len(newPodList) - 1)
if _, err := psc.syncPetSet(ps, pl); err != nil { if _, err := psc.syncStatefulSet(ps, pl); err != nil {
t.Errorf("Error syncing PetSet: %v", err) t.Errorf("Error syncing StatefulSet: %v", err)
} }
found := false found := false
@ -234,55 +234,55 @@ func TestPetSetControllerBlocksScaling(t *testing.T) {
} }
} }
func TestPetSetBlockingPetIsCleared(t *testing.T) { func TestStatefulSetBlockingPetIsCleared(t *testing.T) {
psc, fc := newFakePetSetController() psc, fc := newFakeStatefulSetController()
ps := newPetSet(3) ps := newStatefulSet(3)
scalePetSet(t, ps, psc, fc, 1) scaleStatefulSet(t, ps, psc, fc, 1)
if blocking, err := psc.blockingPetStore.Get(ps, fc.getPodList()); err != nil || blocking != nil { if blocking, err := psc.blockingPetStore.Get(ps, fc.getPodList()); err != nil || blocking != nil {
t.Errorf("Unexpected blocking pet %v, err %v", blocking, err) t.Errorf("Unexpected blocking pod %v, err %v", blocking, err)
} }
// 1 not yet healthy pet // 1 not yet healthy pet
psc.syncPetSet(ps, fc.getPodList()) psc.syncStatefulSet(ps, fc.getPodList())
if blocking, err := psc.blockingPetStore.Get(ps, fc.getPodList()); err != nil || blocking == nil { if blocking, err := psc.blockingPetStore.Get(ps, fc.getPodList()); err != nil || blocking == nil {
t.Errorf("Expected blocking pet %v, err %v", blocking, err) t.Errorf("Expected blocking pod %v, err %v", blocking, err)
} }
// Deleting the petset should clear the blocking pet // Deleting the statefulset should clear the blocking pet
if err := psc.psStore.Store.Delete(ps); err != nil { if err := psc.psStore.Store.Delete(ps); err != nil {
t.Fatalf("Unable to delete pet %v from petset controller store.", ps.Name) t.Fatalf("Unable to delete pod %v from statefulset controller store.", ps.Name)
} }
if err := psc.Sync(fmt.Sprintf("%v/%v", ps.Namespace, ps.Name)); err != nil { if err := psc.Sync(fmt.Sprintf("%v/%v", ps.Namespace, ps.Name)); err != nil {
t.Errorf("Error during sync of deleted petset %v", err) t.Errorf("Error during sync of deleted statefulset %v", err)
} }
fc.pets = []*pcb{} fc.pets = []*pcb{}
fc.petsCreated = 0 fc.petsCreated = 0
if blocking, err := psc.blockingPetStore.Get(ps, fc.getPodList()); err != nil || blocking != nil { if blocking, err := psc.blockingPetStore.Get(ps, fc.getPodList()); err != nil || blocking != nil {
t.Errorf("Unexpected blocking pet %v, err %v", blocking, err) t.Errorf("Unexpected blocking pod %v, err %v", blocking, err)
} }
saturatePetSet(t, ps, psc, fc) saturateStatefulSet(t, ps, psc, fc)
// Make sure we don't leak the final blockin pet in the store // Make sure we don't leak the final blockin pet in the store
psc.syncPetSet(ps, fc.getPodList()) psc.syncStatefulSet(ps, fc.getPodList())
if p, exists, err := psc.blockingPetStore.store.GetByKey(fmt.Sprintf("%v/%v", ps.Namespace, ps.Name)); err != nil || exists { if p, exists, err := psc.blockingPetStore.store.GetByKey(fmt.Sprintf("%v/%v", ps.Namespace, ps.Name)); err != nil || exists {
t.Errorf("Unexpected blocking pet, err %v: %+v", err, p) t.Errorf("Unexpected blocking pod, err %v: %+v", err, p)
} }
} }
func TestSyncPetSetBlockedPet(t *testing.T) { func TestSyncStatefulSetBlockedPet(t *testing.T) {
psc, fc := newFakePetSetController() psc, fc := newFakeStatefulSetController()
ps := newPetSet(3) ps := newStatefulSet(3)
i, _ := psc.syncPetSet(ps, fc.getPodList()) i, _ := psc.syncStatefulSet(ps, fc.getPodList())
if i != len(fc.getPodList()) { if i != len(fc.getPodList()) {
t.Errorf("syncPetSet should return actual amount of pods") t.Errorf("syncStatefulSet should return actual amount of pods")
} }
} }
type fakeClient struct { type fakeClient struct {
fake_internal.Clientset fake_internal.Clientset
petSetClient *fakePetSetClient statefulsetClient *fakeStatefulSetClient
} }
func (c *fakeClient) Apps() unversioned.AppsInterface { func (c *fakeClient) Apps() unversioned.AppsInterface {
@ -294,38 +294,38 @@ type fakeApps struct {
*fake.FakeApps *fake.FakeApps
} }
func (c *fakeApps) PetSets(namespace string) unversioned.PetSetInterface { func (c *fakeApps) StatefulSets(namespace string) unversioned.StatefulSetInterface {
c.petSetClient.Namespace = namespace c.statefulsetClient.Namespace = namespace
return c.petSetClient return c.statefulsetClient
} }
type fakePetSetClient struct { type fakeStatefulSetClient struct {
*fake.FakePetSets *fake.FakeStatefulSets
Namespace string Namespace string
replicas int32 replicas int32
} }
func (f *fakePetSetClient) UpdateStatus(petSet *apps.PetSet) (*apps.PetSet, error) { func (f *fakeStatefulSetClient) UpdateStatus(statefulset *apps.StatefulSet) (*apps.StatefulSet, error) {
f.replicas = petSet.Status.Replicas f.replicas = statefulset.Status.Replicas
return petSet, nil return statefulset, nil
} }
func TestPetSetReplicaCount(t *testing.T) { func TestStatefulSetReplicaCount(t *testing.T) {
fpsc := &fakePetSetClient{} fpsc := &fakeStatefulSetClient{}
psc, _ := newFakePetSetController() psc, _ := newFakeStatefulSetController()
psc.kubeClient = &fakeClient{ psc.kubeClient = &fakeClient{
petSetClient: fpsc, statefulsetClient: fpsc,
} }
ps := newPetSet(3) ps := newStatefulSet(3)
psKey := fmt.Sprintf("%v/%v", ps.Namespace, ps.Name) psKey := fmt.Sprintf("%v/%v", ps.Namespace, ps.Name)
psc.psStore.Store.Add(ps) psc.psStore.Store.Add(ps)
if err := psc.Sync(psKey); err != nil { if err := psc.Sync(psKey); err != nil {
t.Errorf("Error during sync of deleted petset %v", err) t.Errorf("Error during sync of deleted statefulset %v", err)
} }
if fpsc.replicas != 1 { if fpsc.replicas != 1 {
t.Errorf("Replicas count sent as status update for PetSet should be 1, is %d instead", fpsc.replicas) t.Errorf("Replicas count sent as status update for StatefulSet should be 1, is %d instead", fpsc.replicas)
} }
} }

View File

@ -29,55 +29,55 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
// overlappingPetSets sorts a list of PetSets by creation timestamp, using their names as a tie breaker. // overlappingStatefulSets sorts a list of StatefulSets by creation timestamp, using their names as a tie breaker.
// Generally used to tie break between PetSets that have overlapping selectors. // Generally used to tie break between StatefulSets that have overlapping selectors.
type overlappingPetSets []apps.PetSet type overlappingStatefulSets []apps.StatefulSet
func (o overlappingPetSets) Len() int { return len(o) } func (o overlappingStatefulSets) Len() int { return len(o) }
func (o overlappingPetSets) Swap(i, j int) { o[i], o[j] = o[j], o[i] } func (o overlappingStatefulSets) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o overlappingPetSets) Less(i, j int) bool { func (o overlappingStatefulSets) Less(i, j int) bool {
if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) {
return o[i].Name < o[j].Name return o[i].Name < o[j].Name
} }
return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) return o[i].CreationTimestamp.Before(o[j].CreationTimestamp)
} }
// updatePetCount attempts to update the Status.Replicas of the given PetSet, with a single GET/PUT retry. // updatePetCount attempts to update the Status.Replicas of the given StatefulSet, with a single GET/PUT retry.
func updatePetCount(psClient appsclientset.PetSetsGetter, ps apps.PetSet, numPets int) (updateErr error) { func updatePetCount(psClient appsclientset.StatefulSetsGetter, ps apps.StatefulSet, numPets int) (updateErr error) {
if ps.Status.Replicas == int32(numPets) || psClient == nil { if ps.Status.Replicas == int32(numPets) || psClient == nil {
return nil return nil
} }
var getErr error var getErr error
for i, ps := 0, &ps; ; i++ { for i, ps := 0, &ps; ; i++ {
glog.V(4).Infof(fmt.Sprintf("Updating replica count for PetSet: %s/%s, ", ps.Namespace, ps.Name) + glog.V(4).Infof(fmt.Sprintf("Updating replica count for StatefulSet: %s/%s, ", ps.Namespace, ps.Name) +
fmt.Sprintf("replicas %d->%d (need %d), ", ps.Status.Replicas, numPets, ps.Spec.Replicas)) fmt.Sprintf("replicas %d->%d (need %d), ", ps.Status.Replicas, numPets, ps.Spec.Replicas))
ps.Status = apps.PetSetStatus{Replicas: int32(numPets)} ps.Status = apps.StatefulSetStatus{Replicas: int32(numPets)}
_, updateErr = psClient.PetSets(ps.Namespace).UpdateStatus(ps) _, updateErr = psClient.StatefulSets(ps.Namespace).UpdateStatus(ps)
if updateErr == nil || i >= statusUpdateRetries { if updateErr == nil || i >= statusUpdateRetries {
return updateErr return updateErr
} }
if ps, getErr = psClient.PetSets(ps.Namespace).Get(ps.Name); getErr != nil { if ps, getErr = psClient.StatefulSets(ps.Namespace).Get(ps.Name); getErr != nil {
return getErr return getErr
} }
} }
} }
// unhealthyPetTracker tracks unhealthy pets for petsets. // unhealthyPetTracker tracks unhealthy pets for statefulsets.
type unhealthyPetTracker struct { type unhealthyPetTracker struct {
pc petClient pc petClient
store cache.Store store cache.Store
storeLock sync.Mutex storeLock sync.Mutex
} }
// Get returns a previously recorded blocking pet for the given petset. // Get returns a previously recorded blocking pet for the given statefulset.
func (u *unhealthyPetTracker) Get(ps *apps.PetSet, knownPets []*api.Pod) (*pcb, error) { func (u *unhealthyPetTracker) Get(ps *apps.StatefulSet, knownPets []*api.Pod) (*pcb, error) {
u.storeLock.Lock() u.storeLock.Lock()
defer u.storeLock.Unlock() defer u.storeLock.Unlock()
// We "Get" by key but "Add" by object because the store interface doesn't // We "Get" by key but "Add" by object because the store interface doesn't
// allow us to Get/Add a related obj (eg petset: blocking pet). // allow us to Get/Add a related obj (eg statefulset: blocking pet).
key, err := controller.KeyFunc(ps) key, err := controller.KeyFunc(ps)
if err != nil { if err != nil {
return nil, err return nil, err
@ -93,16 +93,16 @@ func (u *unhealthyPetTracker) Get(ps *apps.PetSet, knownPets []*api.Pod) (*pcb,
if !exists { if !exists {
for _, p := range knownPets { for _, p := range knownPets {
if hc.isHealthy(p) && !hc.isDying(p) { if hc.isHealthy(p) && !hc.isDying(p) {
glog.V(4).Infof("Ignoring healthy pet %v for PetSet %v", p.Name, ps.Name) glog.V(4).Infof("Ignoring healthy pod %v for StatefulSet %v", p.Name, ps.Name)
continue continue
} }
glog.Infof("No recorded blocking pet, but found unhealthy pet %v for PetSet %v", p.Name, ps.Name) glog.Infof("No recorded blocking pod, but found unhealthy pod %v for StatefulSet %v", p.Name, ps.Name)
return &pcb{pod: p, parent: ps}, nil return &pcb{pod: p, parent: ps}, nil
} }
return nil, nil return nil, nil
} }
// This is a pet that's blocking further creates/deletes of a petset. If it // This is a pet that's blocking further creates/deletes of a statefulset. If it
// disappears, it's no longer blocking. If it exists, it continues to block // disappears, it's no longer blocking. If it exists, it continues to block
// till it turns healthy or disappears. // till it turns healthy or disappears.
bp := obj.(*pcb) bp := obj.(*pcb)
@ -111,12 +111,12 @@ func (u *unhealthyPetTracker) Get(ps *apps.PetSet, knownPets []*api.Pod) (*pcb,
return nil, err return nil, err
} }
if !exists { if !exists {
glog.V(4).Infof("Clearing blocking pet %v for PetSet %v because it's been deleted", bp.pod.Name, ps.Name) glog.V(4).Infof("Clearing blocking pod %v for StatefulSet %v because it's been deleted", bp.pod.Name, ps.Name)
return nil, nil return nil, nil
} }
blockingPetPod := blockingPet.pod blockingPetPod := blockingPet.pod
if hc.isHealthy(blockingPetPod) && !hc.isDying(blockingPetPod) { if hc.isHealthy(blockingPetPod) && !hc.isDying(blockingPetPod) {
glog.V(4).Infof("Clearing blocking pet %v for PetSet %v because it's healthy", bp.pod.Name, ps.Name) glog.V(4).Infof("Clearing blocking pod %v for StatefulSet %v because it's healthy", bp.pod.Name, ps.Name)
u.store.Delete(blockingPet) u.store.Delete(blockingPet)
blockingPet = nil blockingPet = nil
} }
@ -131,11 +131,11 @@ func (u *unhealthyPetTracker) Add(blockingPet *pcb) error {
if blockingPet == nil { if blockingPet == nil {
return nil return nil
} }
glog.V(4).Infof("Adding blocking pet %v for PetSet %v", blockingPet.pod.Name, blockingPet.parent.Name) glog.V(4).Infof("Adding blocking pod %v for StatefulSet %v", blockingPet.pod.Name, blockingPet.parent.Name)
return u.store.Add(blockingPet) return u.store.Add(blockingPet)
} }
// newUnHealthyPetTracker tracks unhealthy pets that block progress of petsets. // newUnHealthyPetTracker tracks unhealthy pets that block progress of statefulsets.
func newUnHealthyPetTracker(pc petClient) *unhealthyPetTracker { func newUnHealthyPetTracker(pc petClient) *unhealthyPetTracker {
return &unhealthyPetTracker{pc: pc, store: cache.NewStore(pcbKeyFunc)} return &unhealthyPetTracker{pc: pc, store: cache.NewStore(pcbKeyFunc)}
} }
@ -148,10 +148,10 @@ func pcbKeyFunc(obj interface{}) (string, error) {
} }
p, ok := obj.(*pcb) p, ok := obj.(*pcb)
if !ok { if !ok {
return "", fmt.Errorf("not a valid pet control block %#v", p) return "", fmt.Errorf("not a valid pod control block %#v", p)
} }
if p.parent == nil { if p.parent == nil {
return "", fmt.Errorf("cannot compute pet control block key without parent pointer %#v", p) return "", fmt.Errorf("cannot compute pod control block key without parent pointer %#v", p)
} }
return controller.KeyFunc(p.parent) return controller.KeyFunc(p.parent)
} }

View File

@ -183,7 +183,7 @@ __custom_func() {
* limitranges (aka 'limits') * limitranges (aka 'limits')
* nodes (aka 'no') * nodes (aka 'no')
* namespaces (aka 'ns') * namespaces (aka 'ns')
* petsets (alpha feature, may be unstable) * statefulsets (alpha feature, may be unstable)
* pods (aka 'po') * pods (aka 'po')
* persistentvolumes (aka 'pv') * persistentvolumes (aka 'pv')
* persistentvolumeclaims (aka 'pvc') * persistentvolumeclaims (aka 'pvc')

View File

@ -863,7 +863,7 @@ func (f *factory) UpdatePodSpecForObject(obj runtime.Object, fn func(*api.PodSpe
return true, fn(&t.Spec.Template.Spec) return true, fn(&t.Spec.Template.Spec)
case *extensions.ReplicaSet: case *extensions.ReplicaSet:
return true, fn(&t.Spec.Template.Spec) return true, fn(&t.Spec.Template.Spec)
case *apps.PetSet: case *apps.StatefulSet:
return true, fn(&t.Spec.Template.Spec) return true, fn(&t.Spec.Template.Spec)
case *batch.Job: case *batch.Job:
return true, fn(&t.Spec.Template.Spec) return true, fn(&t.Spec.Template.Spec)

View File

@ -736,12 +736,12 @@ func TestDiscoveryReplaceAliases(t *testing.T) {
{ {
name: "all-replacement", name: "all-replacement",
arg: "all", arg: "all",
expected: "pods,replicationcontrollers,services,petsets,horizontalpodautoscalers,jobs,deployments,replicasets", expected: "pods,replicationcontrollers,services,statefulsets,horizontalpodautoscalers,jobs,deployments,replicasets",
}, },
{ {
name: "alias-in-comma-separated-arg", name: "alias-in-comma-separated-arg",
arg: "all,secrets", arg: "all,secrets",
expected: "pods,replicationcontrollers,services,petsets,horizontalpodautoscalers,jobs,deployments,replicasets,secrets", expected: "pods,replicationcontrollers,services,statefulsets,horizontalpodautoscalers,jobs,deployments,replicasets,secrets",
}, },
} }

View File

@ -111,7 +111,7 @@ var userResources = []unversioned.GroupResource{
{Group: "", Resource: "pods"}, {Group: "", Resource: "pods"},
{Group: "", Resource: "replicationcontrollers"}, {Group: "", Resource: "replicationcontrollers"},
{Group: "", Resource: "services"}, {Group: "", Resource: "services"},
{Group: "apps", Resource: "petsets"}, {Group: "apps", Resource: "statefulsets"},
{Group: "autoscaling", Resource: "horizontalpodautoscalers"}, {Group: "autoscaling", Resource: "horizontalpodautoscalers"},
{Group: "extensions", Resource: "jobs"}, {Group: "extensions", Resource: "jobs"},
{Group: "extensions", Resource: "deployments"}, {Group: "extensions", Resource: "deployments"},

View File

@ -37,12 +37,12 @@ func TestReplaceAliases(t *testing.T) {
{ {
name: "all-replacement", name: "all-replacement",
arg: "all", arg: "all",
expected: "pods,replicationcontrollers,services,petsets,horizontalpodautoscalers,jobs,deployments,replicasets", expected: "pods,replicationcontrollers,services,statefulsets,horizontalpodautoscalers,jobs,deployments,replicasets",
}, },
{ {
name: "alias-in-comma-separated-arg", name: "alias-in-comma-separated-arg",
arg: "all,secrets", arg: "all,secrets",
expected: "pods,replicationcontrollers,services,petsets,horizontalpodautoscalers,jobs,deployments,replicasets,secrets", expected: "pods,replicationcontrollers,services,statefulsets,horizontalpodautoscalers,jobs,deployments,replicasets,secrets",
}, },
} }

View File

@ -117,7 +117,7 @@ func describerMap(c clientset.Interface) map[unversioned.GroupKind]Describer {
extensions.Kind("Ingress"): &IngressDescriber{c}, extensions.Kind("Ingress"): &IngressDescriber{c},
batch.Kind("Job"): &JobDescriber{c}, batch.Kind("Job"): &JobDescriber{c},
batch.Kind("ScheduledJob"): &ScheduledJobDescriber{c}, batch.Kind("ScheduledJob"): &ScheduledJobDescriber{c},
apps.Kind("PetSet"): &PetSetDescriber{c}, apps.Kind("StatefulSet"): &StatefulSetDescriber{c},
certificates.Kind("CertificateSigningRequest"): &CertificateSigningRequestDescriber{c}, certificates.Kind("CertificateSigningRequest"): &CertificateSigningRequestDescriber{c},
storage.Kind("StorageClass"): &StorageClassDescriber{c}, storage.Kind("StorageClass"): &StorageClassDescriber{c},
} }
@ -1863,12 +1863,12 @@ func describeNode(node *api.Node, nodeNonTerminatedPodsList *api.PodList, events
}) })
} }
type PetSetDescriber struct { type StatefulSetDescriber struct {
client clientset.Interface client clientset.Interface
} }
func (p *PetSetDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) { func (p *StatefulSetDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) {
ps, err := p.client.Apps().PetSets(namespace).Get(name) ps, err := p.client.Apps().StatefulSets(namespace).Get(name)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -470,7 +470,7 @@ var (
scheduledJobColumns = []string{"NAME", "SCHEDULE", "SUSPEND", "ACTIVE", "LAST-SCHEDULE"} scheduledJobColumns = []string{"NAME", "SCHEDULE", "SUSPEND", "ACTIVE", "LAST-SCHEDULE"}
serviceColumns = []string{"NAME", "CLUSTER-IP", "EXTERNAL-IP", "PORT(S)", "AGE"} serviceColumns = []string{"NAME", "CLUSTER-IP", "EXTERNAL-IP", "PORT(S)", "AGE"}
ingressColumns = []string{"NAME", "HOSTS", "ADDRESS", "PORTS", "AGE"} ingressColumns = []string{"NAME", "HOSTS", "ADDRESS", "PORTS", "AGE"}
petSetColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"} statefulSetColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"}
endpointColumns = []string{"NAME", "ENDPOINTS", "AGE"} endpointColumns = []string{"NAME", "ENDPOINTS", "AGE"}
nodeColumns = []string{"NAME", "STATUS", "AGE"} nodeColumns = []string{"NAME", "STATUS", "AGE"}
daemonSetColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "NODE-SELECTOR", "AGE"} daemonSetColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "NODE-SELECTOR", "AGE"}
@ -539,8 +539,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(serviceColumns, printServiceList) h.Handler(serviceColumns, printServiceList)
h.Handler(ingressColumns, printIngress) h.Handler(ingressColumns, printIngress)
h.Handler(ingressColumns, printIngressList) h.Handler(ingressColumns, printIngressList)
h.Handler(petSetColumns, printPetSet) h.Handler(statefulSetColumns, printStatefulSet)
h.Handler(petSetColumns, printPetSetList) h.Handler(statefulSetColumns, printStatefulSetList)
h.Handler(endpointColumns, printEndpoints) h.Handler(endpointColumns, printEndpoints)
h.Handler(endpointColumns, printEndpointsList) h.Handler(endpointColumns, printEndpointsList)
h.Handler(nodeColumns, printNode) h.Handler(nodeColumns, printNode)
@ -1227,7 +1227,7 @@ func printIngressList(ingressList *extensions.IngressList, w io.Writer, options
return nil return nil
} }
func printPetSet(ps *apps.PetSet, w io.Writer, options PrintOptions) error { func printStatefulSet(ps *apps.StatefulSet, w io.Writer, options PrintOptions) error {
name := formatResourceName(options.Kind, ps.Name, options.WithKind) name := formatResourceName(options.Kind, ps.Name, options.WithKind)
namespace := ps.Namespace namespace := ps.Namespace
@ -1266,9 +1266,9 @@ func printPetSet(ps *apps.PetSet, w io.Writer, options PrintOptions) error {
return nil return nil
} }
func printPetSetList(petSetList *apps.PetSetList, w io.Writer, options PrintOptions) error { func printStatefulSetList(statefulSetList *apps.StatefulSetList, w io.Writer, options PrintOptions) error {
for _, ps := range petSetList.Items { for _, ps := range statefulSetList.Items {
if err := printPetSet(&ps, w, options); err != nil { if err := printStatefulSet(&ps, w, options); err != nil {
return err return err
} }
} }

View File

@ -57,8 +57,8 @@ func ScalerFor(kind unversioned.GroupKind, c internalclientset.Interface) (Scale
return &ReplicaSetScaler{c.Extensions()}, nil return &ReplicaSetScaler{c.Extensions()}, nil
case extensions.Kind("Job"), batch.Kind("Job"): case extensions.Kind("Job"), batch.Kind("Job"):
return &JobScaler{c.Batch()}, nil // Either kind of job can be scaled with Batch interface. return &JobScaler{c.Batch()}, nil // Either kind of job can be scaled with Batch interface.
case apps.Kind("PetSet"): case apps.Kind("StatefulSet"):
return &PetSetScaler{c.Apps()}, nil return &StatefulSetScaler{c.Apps()}, nil
case extensions.Kind("Deployment"): case extensions.Kind("Deployment"):
return &DeploymentScaler{c.Extensions()}, nil return &DeploymentScaler{c.Extensions()}, nil
} }
@ -137,8 +137,8 @@ func ScaleCondition(r Scaler, precondition *ScalePrecondition, namespace, name s
} }
} }
// ValidatePetSet ensures that the preconditions match. Returns nil if they are valid, an error otherwise. // ValidateStatefulSet ensures that the preconditions match. Returns nil if they are valid, an error otherwise.
func (precondition *ScalePrecondition) ValidatePetSet(ps *apps.PetSet) error { func (precondition *ScalePrecondition) ValidateStatefulSet(ps *apps.StatefulSet) error {
if precondition.Size != -1 && int(ps.Spec.Replicas) != precondition.Size { if precondition.Size != -1 && int(ps.Spec.Replicas) != precondition.Size {
return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(int(ps.Spec.Replicas))} return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(int(ps.Spec.Replicas))}
} }
@ -328,34 +328,34 @@ func (precondition *ScalePrecondition) ValidateJob(job *batch.Job) error {
return nil return nil
} }
type PetSetScaler struct { type StatefulSetScaler struct {
c appsclient.PetSetsGetter c appsclient.StatefulSetsGetter
} }
// ScaleSimple does a simple one-shot attempt at scaling. It returns the // ScaleSimple does a simple one-shot attempt at scaling. It returns the
// resourceVersion of the petset if the update is successful. // resourceVersion of the statefulset if the update is successful.
func (scaler *PetSetScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) (string, error) { func (scaler *StatefulSetScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) (string, error) {
ps, err := scaler.c.PetSets(namespace).Get(name) ps, err := scaler.c.StatefulSets(namespace).Get(name)
if err != nil { if err != nil {
return "", ScaleError{ScaleGetFailure, "Unknown", err} return "", ScaleError{ScaleGetFailure, "Unknown", err}
} }
if preconditions != nil { if preconditions != nil {
if err := preconditions.ValidatePetSet(ps); err != nil { if err := preconditions.ValidateStatefulSet(ps); err != nil {
return "", err return "", err
} }
} }
ps.Spec.Replicas = int32(newSize) ps.Spec.Replicas = int32(newSize)
updatedPetSet, err := scaler.c.PetSets(namespace).Update(ps) updatedStatefulSet, err := scaler.c.StatefulSets(namespace).Update(ps)
if err != nil { if err != nil {
if errors.IsConflict(err) { if errors.IsConflict(err) {
return "", ScaleError{ScaleUpdateConflictFailure, ps.ResourceVersion, err} return "", ScaleError{ScaleUpdateConflictFailure, ps.ResourceVersion, err}
} }
return "", ScaleError{ScaleUpdateFailure, ps.ResourceVersion, err} return "", ScaleError{ScaleUpdateFailure, ps.ResourceVersion, err}
} }
return updatedPetSet.ResourceVersion, nil return updatedStatefulSet.ResourceVersion, nil
} }
func (scaler *PetSetScaler) Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams) error { func (scaler *StatefulSetScaler) Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams) error {
if preconditions == nil { if preconditions == nil {
preconditions = &ScalePrecondition{-1, ""} preconditions = &ScalePrecondition{-1, ""}
} }
@ -368,11 +368,11 @@ func (scaler *PetSetScaler) Scale(namespace, name string, newSize uint, precondi
return err return err
} }
if waitForReplicas != nil { if waitForReplicas != nil {
job, err := scaler.c.PetSets(namespace).Get(name) job, err := scaler.c.StatefulSets(namespace).Get(name)
if err != nil { if err != nil {
return err return err
} }
err = wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout, client.PetSetHasDesiredPets(scaler.c, job)) err = wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout, client.StatefulSetHasDesiredPets(scaler.c, job))
if err == wait.ErrWaitTimeout { if err == wait.ErrWaitTimeout {
return fmt.Errorf("timed out waiting for %q to be synced", name) return fmt.Errorf("timed out waiting for %q to be synced", name)
} }

View File

@ -87,8 +87,8 @@ func ReaperFor(kind unversioned.GroupKind, c internalclientset.Interface) (Reape
case extensions.Kind("Job"), batch.Kind("Job"): case extensions.Kind("Job"), batch.Kind("Job"):
return &JobReaper{c.Batch(), c.Core(), Interval, Timeout}, nil return &JobReaper{c.Batch(), c.Core(), Interval, Timeout}, nil
case apps.Kind("PetSet"): case apps.Kind("StatefulSet"):
return &PetSetReaper{c.Apps(), c.Core(), Interval, Timeout}, nil return &StatefulSetReaper{c.Apps(), c.Core(), Interval, Timeout}, nil
case extensions.Kind("Deployment"): case extensions.Kind("Deployment"):
return &DeploymentReaper{c.Extensions(), c.Extensions(), Interval, Timeout}, nil return &DeploymentReaper{c.Extensions(), c.Extensions(), Interval, Timeout}, nil
@ -129,8 +129,8 @@ type PodReaper struct {
type ServiceReaper struct { type ServiceReaper struct {
client coreclient.ServicesGetter client coreclient.ServicesGetter
} }
type PetSetReaper struct { type StatefulSetReaper struct {
client appsclient.PetSetsGetter client appsclient.StatefulSetsGetter
podClient coreclient.PodsGetter podClient coreclient.PodsGetter
pollInterval, timeout time.Duration pollInterval, timeout time.Duration
} }
@ -325,10 +325,10 @@ func (reaper *DaemonSetReaper) Stop(namespace, name string, timeout time.Duratio
return reaper.client.DaemonSets(namespace).Delete(name, nil) return reaper.client.DaemonSets(namespace).Delete(name, nil)
} }
func (reaper *PetSetReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error { func (reaper *StatefulSetReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
petsets := reaper.client.PetSets(namespace) statefulsets := reaper.client.StatefulSets(namespace)
scaler := &PetSetScaler{reaper.client} scaler := &StatefulSetScaler{reaper.client}
ps, err := petsets.Get(name) ps, err := statefulsets.Get(name)
if err != nil { if err != nil {
return err return err
} }
@ -337,13 +337,13 @@ func (reaper *PetSetReaper) Stop(namespace, name string, timeout time.Duration,
timeout = Timeout + time.Duration(10*numPets)*time.Second timeout = Timeout + time.Duration(10*numPets)*time.Second
} }
retry := NewRetryParams(reaper.pollInterval, reaper.timeout) retry := NewRetryParams(reaper.pollInterval, reaper.timeout)
waitForPetSet := NewRetryParams(reaper.pollInterval, reaper.timeout) waitForStatefulSet := NewRetryParams(reaper.pollInterval, reaper.timeout)
if err = scaler.Scale(namespace, name, 0, nil, retry, waitForPetSet); err != nil { if err = scaler.Scale(namespace, name, 0, nil, retry, waitForStatefulSet); err != nil {
return err return err
} }
// TODO: This shouldn't be needed, see corresponding TODO in PetSetHasDesiredPets. // TODO: This shouldn't be needed, see corresponding TODO in StatefulSetHasDesiredPets.
// PetSet should track generation number. // StatefulSet should track generation number.
pods := reaper.podClient.Pods(namespace) pods := reaper.podClient.Pods(namespace)
selector, _ := unversioned.LabelSelectorAsSelector(ps.Spec.Selector) selector, _ := unversioned.LabelSelectorAsSelector(ps.Spec.Selector)
options := api.ListOptions{LabelSelector: selector} options := api.ListOptions{LabelSelector: selector}
@ -365,8 +365,8 @@ func (reaper *PetSetReaper) Stop(namespace, name string, timeout time.Duration,
} }
// TODO: Cleanup volumes? We don't want to accidentally delete volumes from // TODO: Cleanup volumes? We don't want to accidentally delete volumes from
// stop, so just leave this up to the petset. // stop, so just leave this up to the statefulset.
return petsets.Delete(name, nil) return statefulsets.Delete(name, nil)
} }
func (reaper *JobReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error { func (reaper *JobReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {

View File

@ -37,11 +37,11 @@ type REST struct {
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/" + opts.ResourcePrefix prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &appsapi.PetSetList{} } newListFunc := func() runtime.Object { return &appsapi.StatefulSetList{} }
storageInterface, dFunc := opts.Decorator( storageInterface, dFunc := opts.Decorator(
opts.StorageConfig, opts.StorageConfig,
cachesize.GetWatchCacheSizeByResource(cachesize.PetSet), cachesize.GetWatchCacheSizeByResource(cachesize.StatefulSet),
&appsapi.PetSet{}, &appsapi.StatefulSet{},
prefix, prefix,
petset.Strategy, petset.Strategy,
newListFunc, newListFunc,
@ -49,27 +49,27 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
) )
store := &registry.Store{ store := &registry.Store{
NewFunc: func() runtime.Object { return &appsapi.PetSet{} }, NewFunc: func() runtime.Object { return &appsapi.StatefulSet{} },
// NewListFunc returns an object capable of storing results of an etcd list. // NewListFunc returns an object capable of storing results of an etcd list.
NewListFunc: newListFunc, NewListFunc: newListFunc,
// Produces a petSet that etcd understands, to the root of the resource // Produces a statefulSet that etcd understands, to the root of the resource
// by combining the namespace in the context with the given prefix // by combining the namespace in the context with the given prefix
KeyRootFunc: func(ctx api.Context) string { KeyRootFunc: func(ctx api.Context) string {
return registry.NamespaceKeyRootFunc(ctx, prefix) return registry.NamespaceKeyRootFunc(ctx, prefix)
}, },
// Produces a petSet that etcd understands, to the resource by combining // Produces a statefulSet that etcd understands, to the resource by combining
// the namespace in the context with the given prefix // the namespace in the context with the given prefix
KeyFunc: func(ctx api.Context, name string) (string, error) { KeyFunc: func(ctx api.Context, name string) (string, error) {
return registry.NamespaceKeyFunc(ctx, prefix, name) return registry.NamespaceKeyFunc(ctx, prefix, name)
}, },
// Retrieve the name field of a replication controller // Retrieve the name field of a replication controller
ObjectNameFunc: func(obj runtime.Object) (string, error) { ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*appsapi.PetSet).Name, nil return obj.(*appsapi.StatefulSet).Name, nil
}, },
// Used to match objects based on labels/fields for list and watch // Used to match objects based on labels/fields for list and watch
PredicateFunc: petset.MatchPetSet, PredicateFunc: petset.MatchStatefulSet,
QualifiedResource: appsapi.Resource("petsets"), QualifiedResource: appsapi.Resource("statefulsets"),
EnableGarbageCollection: opts.EnableGarbageCollection, EnableGarbageCollection: opts.EnableGarbageCollection,
DeleteCollectionWorkers: opts.DeleteCollectionWorkers, DeleteCollectionWorkers: opts.DeleteCollectionWorkers,
@ -88,13 +88,13 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
return &REST{store}, &StatusREST{store: &statusStore} return &REST{store}, &StatusREST{store: &statusStore}
} }
// StatusREST implements the REST endpoint for changing the status of an petSet // StatusREST implements the REST endpoint for changing the status of an statefulSet
type StatusREST struct { type StatusREST struct {
store *registry.Store store *registry.Store
} }
func (r *StatusREST) New() runtime.Object { func (r *StatusREST) New() runtime.Object {
return &appsapi.PetSet{} return &appsapi.StatefulSet{}
} }
// Get retrieves the object from the storage. It is required to support Patch. // Get retrieves the object from the storage. It is required to support Patch.

View File

@ -33,30 +33,30 @@ import (
func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, apps.GroupName) etcdStorage, server := registrytest.NewEtcdStorage(t, apps.GroupName)
restOptions := generic.RESTOptions{StorageConfig: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "petsets"} restOptions := generic.RESTOptions{StorageConfig: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "statefulsets"}
petSetStorage, statusStorage := NewREST(restOptions) statefulSetStorage, statusStorage := NewREST(restOptions)
return petSetStorage, statusStorage, server return statefulSetStorage, statusStorage, server
} }
// createPetSet is a helper function that returns a PetSet with the updated resource version. // createStatefulSet is a helper function that returns a StatefulSet with the updated resource version.
func createPetSet(storage *REST, ps apps.PetSet, t *testing.T) (apps.PetSet, error) { func createStatefulSet(storage *REST, ps apps.StatefulSet, t *testing.T) (apps.StatefulSet, error) {
ctx := api.WithNamespace(api.NewContext(), ps.Namespace) ctx := api.WithNamespace(api.NewContext(), ps.Namespace)
obj, err := storage.Create(ctx, &ps) obj, err := storage.Create(ctx, &ps)
if err != nil { if err != nil {
t.Errorf("Failed to create PetSet, %v", err) t.Errorf("Failed to create StatefulSet, %v", err)
} }
newPS := obj.(*apps.PetSet) newPS := obj.(*apps.StatefulSet)
return *newPS, nil return *newPS, nil
} }
func validNewPetSet() *apps.PetSet { func validNewStatefulSet() *apps.StatefulSet {
return &apps.PetSet{ return &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", Name: "foo",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
Labels: map[string]string{"a": "b"}, Labels: map[string]string{"a": "b"},
}, },
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
@ -76,7 +76,7 @@ func validNewPetSet() *apps.PetSet {
}, },
Replicas: 7, Replicas: 7,
}, },
Status: apps.PetSetStatus{}, Status: apps.StatefulSetStatus{},
} }
} }
@ -85,7 +85,7 @@ func TestCreate(t *testing.T) {
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
test := registrytest.New(t, storage.Store) test := registrytest.New(t, storage.Store)
ps := validNewPetSet() ps := validNewStatefulSet()
ps.ObjectMeta = api.ObjectMeta{} ps.ObjectMeta = api.ObjectMeta{}
test.TestCreate( test.TestCreate(
// valid // valid
@ -101,17 +101,17 @@ func TestStatusUpdate(t *testing.T) {
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault) ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault)
key := etcdtest.AddPrefix("/petsets/" + api.NamespaceDefault + "/foo") key := etcdtest.AddPrefix("/statefulsets/" + api.NamespaceDefault + "/foo")
validPetSet := validNewPetSet() validStatefulSet := validNewStatefulSet()
if err := storage.Storage.Create(ctx, key, validPetSet, nil, 0); err != nil { if err := storage.Storage.Create(ctx, key, validStatefulSet, nil, 0); err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
update := apps.PetSet{ update := apps.StatefulSet{
ObjectMeta: validPetSet.ObjectMeta, ObjectMeta: validStatefulSet.ObjectMeta,
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: 7, Replicas: 7,
}, },
Status: apps.PetSetStatus{ Status: apps.StatefulSetStatus{
Replicas: 7, Replicas: 7,
}, },
} }
@ -124,7 +124,7 @@ func TestStatusUpdate(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
ps := obj.(*apps.PetSet) ps := obj.(*apps.StatefulSet)
if ps.Spec.Replicas != 7 { if ps.Spec.Replicas != 7 {
t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", ps.Spec.Replicas) t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", ps.Spec.Replicas)
} }
@ -138,7 +138,7 @@ func TestGet(t *testing.T) {
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
test := registrytest.New(t, storage.Store) test := registrytest.New(t, storage.Store)
test.TestGet(validNewPetSet()) test.TestGet(validNewStatefulSet())
} }
func TestList(t *testing.T) { func TestList(t *testing.T) {
@ -146,7 +146,7 @@ func TestList(t *testing.T) {
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
test := registrytest.New(t, storage.Store) test := registrytest.New(t, storage.Store)
test.TestList(validNewPetSet()) test.TestList(validNewStatefulSet())
} }
func TestDelete(t *testing.T) { func TestDelete(t *testing.T) {
@ -154,7 +154,7 @@ func TestDelete(t *testing.T) {
defer server.Terminate(t) defer server.Terminate(t)
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
test := registrytest.New(t, storage.Store) test := registrytest.New(t, storage.Store)
test.TestDelete(validNewPetSet()) test.TestDelete(validNewStatefulSet())
} }
func TestWatch(t *testing.T) { func TestWatch(t *testing.T) {
@ -163,7 +163,7 @@ func TestWatch(t *testing.T) {
defer storage.Store.DestroyFunc() defer storage.Store.DestroyFunc()
test := registrytest.New(t, storage.Store) test := registrytest.New(t, storage.Store)
test.TestWatch( test.TestWatch(
validNewPetSet(), validNewStatefulSet(),
// matching labels // matching labels
[]labels.Set{ []labels.Set{
{"a": "b"}, {"a": "b"},

View File

@ -31,109 +31,109 @@ import (
"k8s.io/kubernetes/pkg/util/validation/field" "k8s.io/kubernetes/pkg/util/validation/field"
) )
// petSetStrategy implements verification logic for Replication PetSets. // statefulSetStrategy implements verification logic for Replication StatefulSets.
type petSetStrategy struct { type statefulSetStrategy struct {
runtime.ObjectTyper runtime.ObjectTyper
api.NameGenerator api.NameGenerator
} }
// Strategy is the default logic that applies when creating and updating Replication PetSet objects. // Strategy is the default logic that applies when creating and updating Replication StatefulSet objects.
var Strategy = petSetStrategy{api.Scheme, api.SimpleNameGenerator} var Strategy = statefulSetStrategy{api.Scheme, api.SimpleNameGenerator}
// NamespaceScoped returns true because all PetSet' need to be within a namespace. // NamespaceScoped returns true because all StatefulSet' need to be within a namespace.
func (petSetStrategy) NamespaceScoped() bool { func (statefulSetStrategy) NamespaceScoped() bool {
return true return true
} }
// PrepareForCreate clears the status of an PetSet before creation. // PrepareForCreate clears the status of an StatefulSet before creation.
func (petSetStrategy) PrepareForCreate(ctx api.Context, obj runtime.Object) { func (statefulSetStrategy) PrepareForCreate(ctx api.Context, obj runtime.Object) {
petSet := obj.(*apps.PetSet) statefulSet := obj.(*apps.StatefulSet)
// create cannot set status // create cannot set status
petSet.Status = apps.PetSetStatus{} statefulSet.Status = apps.StatefulSetStatus{}
petSet.Generation = 1 statefulSet.Generation = 1
} }
// PrepareForUpdate clears fields that are not allowed to be set by end users on update. // PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (petSetStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) { func (statefulSetStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) {
newPetSet := obj.(*apps.PetSet) newStatefulSet := obj.(*apps.StatefulSet)
oldPetSet := old.(*apps.PetSet) oldStatefulSet := old.(*apps.StatefulSet)
// Update is not allowed to set status // Update is not allowed to set status
newPetSet.Status = oldPetSet.Status newStatefulSet.Status = oldStatefulSet.Status
// Any changes to the spec increment the generation number, any changes to the // Any changes to the spec increment the generation number, any changes to the
// status should reflect the generation number of the corresponding object. // status should reflect the generation number of the corresponding object.
// See api.ObjectMeta description for more information on Generation. // See api.ObjectMeta description for more information on Generation.
if !reflect.DeepEqual(oldPetSet.Spec, newPetSet.Spec) { if !reflect.DeepEqual(oldStatefulSet.Spec, newStatefulSet.Spec) {
newPetSet.Generation = oldPetSet.Generation + 1 newStatefulSet.Generation = oldStatefulSet.Generation + 1
} }
} }
// Validate validates a new PetSet. // Validate validates a new StatefulSet.
func (petSetStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { func (statefulSetStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
petSet := obj.(*apps.PetSet) statefulSet := obj.(*apps.StatefulSet)
return validation.ValidatePetSet(petSet) return validation.ValidateStatefulSet(statefulSet)
} }
// Canonicalize normalizes the object after validation. // Canonicalize normalizes the object after validation.
func (petSetStrategy) Canonicalize(obj runtime.Object) { func (statefulSetStrategy) Canonicalize(obj runtime.Object) {
} }
// AllowCreateOnUpdate is false for PetSet; this means POST is needed to create one. // AllowCreateOnUpdate is false for StatefulSet; this means POST is needed to create one.
func (petSetStrategy) AllowCreateOnUpdate() bool { func (statefulSetStrategy) AllowCreateOnUpdate() bool {
return false return false
} }
// ValidateUpdate is the default update validation for an end user. // ValidateUpdate is the default update validation for an end user.
func (petSetStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { func (statefulSetStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
validationErrorList := validation.ValidatePetSet(obj.(*apps.PetSet)) validationErrorList := validation.ValidateStatefulSet(obj.(*apps.StatefulSet))
updateErrorList := validation.ValidatePetSetUpdate(obj.(*apps.PetSet), old.(*apps.PetSet)) updateErrorList := validation.ValidateStatefulSetUpdate(obj.(*apps.StatefulSet), old.(*apps.StatefulSet))
return append(validationErrorList, updateErrorList...) return append(validationErrorList, updateErrorList...)
} }
// AllowUnconditionalUpdate is the default update policy for PetSet objects. // AllowUnconditionalUpdate is the default update policy for StatefulSet objects.
func (petSetStrategy) AllowUnconditionalUpdate() bool { func (statefulSetStrategy) AllowUnconditionalUpdate() bool {
return true return true
} }
// PetSetToSelectableFields returns a field set that represents the object. // StatefulSetToSelectableFields returns a field set that represents the object.
func PetSetToSelectableFields(petSet *apps.PetSet) fields.Set { func StatefulSetToSelectableFields(statefulSet *apps.StatefulSet) fields.Set {
return generic.ObjectMetaFieldsSet(&petSet.ObjectMeta, true) return generic.ObjectMetaFieldsSet(&statefulSet.ObjectMeta, true)
} }
// MatchPetSet is the filter used by the generic etcd backend to watch events // MatchStatefulSet is the filter used by the generic etcd backend to watch events
// from etcd to clients of the apiserver only interested in specific labels/fields. // from etcd to clients of the apiserver only interested in specific labels/fields.
func MatchPetSet(label labels.Selector, field fields.Selector) storage.SelectionPredicate { func MatchStatefulSet(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
return storage.SelectionPredicate{ return storage.SelectionPredicate{
Label: label, Label: label,
Field: field, Field: field,
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
petSet, ok := obj.(*apps.PetSet) statefulSet, ok := obj.(*apps.StatefulSet)
if !ok { if !ok {
return nil, nil, fmt.Errorf("given object is not an PetSet.") return nil, nil, fmt.Errorf("given object is not an StatefulSet.")
} }
return labels.Set(petSet.ObjectMeta.Labels), PetSetToSelectableFields(petSet), nil return labels.Set(statefulSet.ObjectMeta.Labels), StatefulSetToSelectableFields(statefulSet), nil
}, },
} }
} }
type petSetStatusStrategy struct { type statefulSetStatusStrategy struct {
petSetStrategy statefulSetStrategy
} }
var StatusStrategy = petSetStatusStrategy{Strategy} var StatusStrategy = statefulSetStatusStrategy{Strategy}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status // PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (petSetStatusStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) { func (statefulSetStatusStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) {
newPetSet := obj.(*apps.PetSet) newStatefulSet := obj.(*apps.StatefulSet)
oldPetSet := old.(*apps.PetSet) oldStatefulSet := old.(*apps.StatefulSet)
// status changes are not allowed to update spec // status changes are not allowed to update spec
newPetSet.Spec = oldPetSet.Spec newStatefulSet.Spec = oldStatefulSet.Spec
} }
// ValidateUpdate is the default update validation for an end user updating status // ValidateUpdate is the default update validation for an end user updating status
func (petSetStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { func (statefulSetStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
// TODO: Validate status updates. // TODO: Validate status updates.
return validation.ValidatePetSetStatusUpdate(obj.(*apps.PetSet), old.(*apps.PetSet)) return validation.ValidateStatefulSetStatusUpdate(obj.(*apps.StatefulSet), old.(*apps.StatefulSet))
} }

View File

@ -24,13 +24,13 @@ import (
"k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps"
) )
func TestPetSetStrategy(t *testing.T) { func TestStatefulSetStrategy(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
if !Strategy.NamespaceScoped() { if !Strategy.NamespaceScoped() {
t.Errorf("PetSet must be namespace scoped") t.Errorf("StatefulSet must be namespace scoped")
} }
if Strategy.AllowCreateOnUpdate() { if Strategy.AllowCreateOnUpdate() {
t.Errorf("PetSet should not allow create on update") t.Errorf("StatefulSet should not allow create on update")
} }
validSelector := map[string]string{"a": "b"} validSelector := map[string]string{"a": "b"}
@ -46,18 +46,18 @@ func TestPetSetStrategy(t *testing.T) {
}, },
}, },
} }
ps := &apps.PetSet{ ps := &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, Selector: &unversioned.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
Status: apps.PetSetStatus{Replicas: 3}, Status: apps.StatefulSetStatus{Replicas: 3},
} }
Strategy.PrepareForCreate(ctx, ps) Strategy.PrepareForCreate(ctx, ps)
if ps.Status.Replicas != 0 { if ps.Status.Replicas != 0 {
t.Error("PetSet should not allow setting status.pets on create") t.Error("StatefulSet should not allow setting status.replicas on create")
} }
errs := Strategy.Validate(ctx, ps) errs := Strategy.Validate(ctx, ps)
if len(errs) != 0 { if len(errs) != 0 {
@ -65,35 +65,35 @@ func TestPetSetStrategy(t *testing.T) {
} }
// Just Spec.Replicas is allowed to change // Just Spec.Replicas is allowed to change
validPs := &apps.PetSet{ validPs := &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1}, ObjectMeta: api.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: ps.Spec.Selector, Selector: ps.Spec.Selector,
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
Status: apps.PetSetStatus{Replicas: 4}, Status: apps.StatefulSetStatus{Replicas: 4},
} }
Strategy.PrepareForUpdate(ctx, validPs, ps) Strategy.PrepareForUpdate(ctx, validPs, ps)
errs = Strategy.ValidateUpdate(ctx, validPs, ps) errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("Updating spec.Replicas is allowed on a petset: %v", errs) t.Errorf("Updating spec.Replicas is allowed on a statefulset: %v", errs)
} }
validPs.Spec.Selector = &unversioned.LabelSelector{MatchLabels: map[string]string{"a": "bar"}} validPs.Spec.Selector = &unversioned.LabelSelector{MatchLabels: map[string]string{"a": "bar"}}
Strategy.PrepareForUpdate(ctx, validPs, ps) Strategy.PrepareForUpdate(ctx, validPs, ps)
errs = Strategy.ValidateUpdate(ctx, validPs, ps) errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("Expected a validation error since updates are disallowed on petsets.") t.Errorf("Expected a validation error since updates are disallowed on statefulsets.")
} }
} }
func TestPetSetStatusStrategy(t *testing.T) { func TestStatefulSetStatusStrategy(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
if !StatusStrategy.NamespaceScoped() { if !StatusStrategy.NamespaceScoped() {
t.Errorf("PetSet must be namespace scoped") t.Errorf("StatefulSet must be namespace scoped")
} }
if StatusStrategy.AllowCreateOnUpdate() { if StatusStrategy.AllowCreateOnUpdate() {
t.Errorf("PetSet should not allow create on update") t.Errorf("StatefulSet should not allow create on update")
} }
validSelector := map[string]string{"a": "b"} validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{ validPodTemplate := api.PodTemplate{
@ -108,34 +108,34 @@ func TestPetSetStatusStrategy(t *testing.T) {
}, },
}, },
} }
oldPS := &apps.PetSet{ oldPS := &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "10"}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "10"},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: 3, Replicas: 3,
Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, Selector: &unversioned.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
Status: apps.PetSetStatus{ Status: apps.StatefulSetStatus{
Replicas: 1, Replicas: 1,
}, },
} }
newPS := &apps.PetSet{ newPS := &apps.StatefulSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "9"}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "9"},
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Replicas: 1, Replicas: 1,
Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, Selector: &unversioned.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template, Template: validPodTemplate.Template,
}, },
Status: apps.PetSetStatus{ Status: apps.StatefulSetStatus{
Replicas: 2, Replicas: 2,
}, },
} }
StatusStrategy.PrepareForUpdate(ctx, newPS, oldPS) StatusStrategy.PrepareForUpdate(ctx, newPS, oldPS)
if newPS.Status.Replicas != 2 { if newPS.Status.Replicas != 2 {
t.Errorf("PetSet status updates should allow change of pets: %v", newPS.Status.Replicas) t.Errorf("StatefulSet status updates should allow change of pods: %v", newPS.Status.Replicas)
} }
if newPS.Spec.Replicas != 3 { if newPS.Spec.Replicas != 3 {
t.Errorf("PetSet status updates should not clobber spec: %v", newPS.Spec) t.Errorf("StatefulSet status updates should not clobber spec: %v", newPS.Spec)
} }
errs := StatusStrategy.ValidateUpdate(ctx, newPS, oldPS) errs := StatusStrategy.ValidateUpdate(ctx, newPS, oldPS)
if len(errs) != 0 { if len(errs) != 0 {

View File

@ -21,7 +21,7 @@ import (
"k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps"
appsapiv1alpha1 "k8s.io/kubernetes/pkg/apis/apps/v1alpha1" appsapiv1alpha1 "k8s.io/kubernetes/pkg/apis/apps/v1alpha1"
"k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/genericapiserver"
petsetetcd "k8s.io/kubernetes/pkg/registry/apps/petset/etcd" statefulsetetcd "k8s.io/kubernetes/pkg/registry/apps/petset/etcd"
) )
type RESTStorageProvider struct{} type RESTStorageProvider struct{}
@ -43,10 +43,10 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource genericapis
version := appsapiv1alpha1.SchemeGroupVersion version := appsapiv1alpha1.SchemeGroupVersion
storage := map[string]rest.Storage{} storage := map[string]rest.Storage{}
if apiResourceConfigSource.ResourceEnabled(version.WithResource("petsets")) { if apiResourceConfigSource.ResourceEnabled(version.WithResource("statefulsets")) {
petsetStorage, petsetStatusStorage := petsetetcd.NewREST(restOptionsGetter(apps.Resource("petsets"))) statefulsetStorage, statefulsetStatusStorage := statefulsetetcd.NewREST(restOptionsGetter(apps.Resource("statefulsets")))
storage["petsets"] = petsetStorage storage["statefulsets"] = statefulsetStorage
storage["petsets/status"] = petsetStatusStorage storage["statefulsets/status"] = statefulsetStatusStorage
} }
return storage return storage
} }

View File

@ -39,7 +39,7 @@ const (
HorizontalPodAutoscalers Resource = "horizontalpodautoscalers" HorizontalPodAutoscalers Resource = "horizontalpodautoscalers"
Ingress Resource = "ingress" Ingress Resource = "ingress"
PodDisruptionBudget Resource = "poddisruptionbudgets" PodDisruptionBudget Resource = "poddisruptionbudgets"
PetSet Resource = "petset" StatefulSet Resource = "statefulset"
Jobs Resource = "jobs" Jobs Resource = "jobs"
LimitRanges Resource = "limitranges" LimitRanges Resource = "limitranges"
Namespaces Resource = "namespaces" Namespaces Resource = "namespaces"

View File

@ -274,7 +274,7 @@ func GetPath(mounter Mounter) (string, error) {
// ChooseZone implements our heuristics for choosing a zone for volume creation based on the volume name // ChooseZone implements our heuristics for choosing a zone for volume creation based on the volume name
// Volumes are generally round-robin-ed across all active zones, using the hash of the PVC Name. // Volumes are generally round-robin-ed across all active zones, using the hash of the PVC Name.
// However, if the PVCName ends with `-<integer>`, we will hash the prefix, and then add the integer to the hash. // However, if the PVCName ends with `-<integer>`, we will hash the prefix, and then add the integer to the hash.
// This means that a PetSet's volumes (`claimname-petsetname-id`) will spread across available zones, // This means that a StatefulSet's volumes (`claimname-statefulsetname-id`) will spread across available zones,
// assuming the id values are consecutive. // assuming the id values are consecutive.
func ChooseZoneForVolume(zones sets.String, pvcName string) string { func ChooseZoneForVolume(zones sets.String, pvcName string) string {
// We create the volume in a zone determined by the name // We create the volume in a zone determined by the name
@ -290,8 +290,8 @@ func ChooseZoneForVolume(zones sets.String, pvcName string) string {
} else { } else {
hashString := pvcName hashString := pvcName
// Heuristic to make sure that volumes in a PetSet are spread across zones // Heuristic to make sure that volumes in a StatefulSet are spread across zones
// PetSet PVCs are (currently) named ClaimName-PetSetName-Id, // StatefulSet PVCs are (currently) named ClaimName-StatefulSetName-Id,
// where Id is an integer index // where Id is an integer index
lastDash := strings.LastIndexByte(pvcName, '-') lastDash := strings.LastIndexByte(pvcName, '-')
if lastDash != -1 { if lastDash != -1 {
@ -302,7 +302,7 @@ func ChooseZoneForVolume(zones sets.String, pvcName string) string {
index = uint32(petID) index = uint32(petID)
// We still hash the volume name, but only the base // We still hash the volume name, but only the base
hashString = pvcName[:lastDash] hashString = pvcName[:lastDash]
glog.V(2).Infof("Detected PetSet-style volume name %q; index=%d", pvcName, index) glog.V(2).Infof("Detected StatefulSet-style volume name %q; index=%d", pvcName, index)
} }
} }
@ -314,7 +314,7 @@ func ChooseZoneForVolume(zones sets.String, pvcName string) string {
// Zones.List returns zones in a consistent order (sorted) // Zones.List returns zones in a consistent order (sorted)
// We do have a potential failure case where volumes will not be properly spread, // We do have a potential failure case where volumes will not be properly spread,
// if the set of zones changes during PetSet volume creation. However, this is // if the set of zones changes during StatefulSet volume creation. However, this is
// probably relatively unlikely because we expect the set of zones to be essentially // probably relatively unlikely because we expect the set of zones to be essentially
// static for clusters. // static for clusters.
// Hopefully we can address this problem if/when we do full scheduler integration of // Hopefully we can address this problem if/when we do full scheduler integration of

View File

@ -82,7 +82,7 @@ func ClusterRoles() []rbac.ClusterRole {
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(), rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(), rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("petsets").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
@ -112,7 +112,7 @@ func ClusterRoles() []rbac.ClusterRole {
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(), rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(), rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("petsets").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(), rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
@ -135,7 +135,7 @@ func ClusterRoles() []rbac.ClusterRole {
// indicator of which namespaces you have access to. // indicator of which namespaces you have access to.
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(), rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule(Read...).Groups(appsGroup).Resources("petsets").RuleOrDie(), rbac.NewRule(Read...).Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule(Read...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(), rbac.NewRule(Read...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),

View File

@ -37,7 +37,7 @@ const (
// GCE instances can have up to 16 PD volumes attached. // GCE instances can have up to 16 PD volumes attached.
DefaultMaxGCEPDVolumes = 16 DefaultMaxGCEPDVolumes = 16
ClusterAutoscalerProvider = "ClusterAutoscalerProvider" ClusterAutoscalerProvider = "ClusterAutoscalerProvider"
PetSetKind = "PetSet" StatefulSetKind = "StatefulSet"
) )
// getMaxVols checks the max PD volumes environment variable, otherwise returning a default value // getMaxVols checks the max PD volumes environment variable, otherwise returning a default value
@ -239,7 +239,7 @@ func GetEquivalencePod(pod *api.Pod) interface{} {
func isValidControllerKind(kind string) bool { func isValidControllerKind(kind string) bool {
switch kind { switch kind {
// list of kinds that we cannot handle // list of kinds that we cannot handle
case PetSetKind: case StatefulSetKind:
return false return false
default: default:
return true return true

View File

@ -244,8 +244,8 @@ var _ = framework.KubeDescribe("[Feature:Example]", func() {
}) })
}) })
framework.KubeDescribe("CassandraPetSet", func() { framework.KubeDescribe("CassandraStatefulSet", func() {
It("should create petset", func() { It("should create statefulset", func() {
mkpath := func(file string) string { mkpath := func(file string) string {
return filepath.Join(framework.TestContext.RepoRoot, "examples/storage/cassandra", file) return filepath.Join(framework.TestContext.RepoRoot, "examples/storage/cassandra", file)
} }
@ -258,9 +258,9 @@ var _ = framework.KubeDescribe("[Feature:Example]", func() {
output := strings.Replace(string(input), "cassandra-0.cassandra.default.svc.cluster.local", "cassandra-0.cassandra."+ns+".svc.cluster.local", -1) output := strings.Replace(string(input), "cassandra-0.cassandra.default.svc.cluster.local", "cassandra-0.cassandra."+ns+".svc.cluster.local", -1)
petSetYaml := "/tmp/cassandra-petset.yaml" statefulsetYaml := "/tmp/cassandra-petset.yaml"
err = ioutil.WriteFile(petSetYaml, []byte(output), 0644) err = ioutil.WriteFile(statefulsetYaml, []byte(output), 0644)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
By("Starting the cassandra service") By("Starting the cassandra service")
@ -269,21 +269,21 @@ var _ = framework.KubeDescribe("[Feature:Example]", func() {
err = framework.WaitForService(c, ns, "cassandra", true, framework.Poll, framework.ServiceRespondingTimeout) err = framework.WaitForService(c, ns, "cassandra", true, framework.Poll, framework.ServiceRespondingTimeout)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Create an PetSet with n nodes in it. Each node will then be verified. // Create an StatefulSet with n nodes in it. Each node will then be verified.
By("Creating a Cassandra PetSet") By("Creating a Cassandra StatefulSet")
framework.RunKubectlOrDie("create", "-f", petSetYaml, nsFlag) framework.RunKubectlOrDie("create", "-f", statefulsetYaml, nsFlag)
petsetPoll := 30 * time.Second statefulsetPoll := 30 * time.Second
petsetTimeout := 10 * time.Minute statefulsetTimeout := 10 * time.Minute
// TODO - parse this number out of the yaml // TODO - parse this number out of the yaml
numPets := 3 numPets := 3
label := labels.SelectorFromSet(labels.Set(map[string]string{"app": "cassandra"})) label := labels.SelectorFromSet(labels.Set(map[string]string{"app": "cassandra"}))
err = wait.PollImmediate(petsetPoll, petsetTimeout, err = wait.PollImmediate(statefulsetPoll, statefulsetTimeout,
func() (bool, error) { func() (bool, error) {
podList, err := c.Core().Pods(ns).List(api.ListOptions{LabelSelector: label}) podList, err := c.Core().Pods(ns).List(api.ListOptions{LabelSelector: label})
if err != nil { if err != nil {
return false, fmt.Errorf("Unable to get list of pods in petset %s", label) return false, fmt.Errorf("Unable to get list of pods in statefulset %s", label)
} }
ExpectNoError(err) ExpectNoError(err)
if len(podList.Items) < numPets { if len(podList.Items) < numPets {
@ -312,8 +312,8 @@ var _ = framework.KubeDescribe("[Feature:Example]", func() {
framework.Failf("Cassandra pod ip %s is not reporting Up and Normal 'UN' via nodetool status", pod.Status.PodIP) framework.Failf("Cassandra pod ip %s is not reporting Up and Normal 'UN' via nodetool status", pod.Status.PodIP)
} }
}) })
// using out of petset e2e as deleting pvc is a pain // using out of statefulset e2e as deleting pvc is a pain
deleteAllPetSets(c, ns) deleteAllStatefulSets(c, ns)
}) })
}) })

View File

@ -110,7 +110,7 @@ func verifyExpectedRcsExistAndGetExpectedPods(c clientset.Interface) ([]string,
if err != nil { if err != nil {
return nil, err return nil, err
} }
psList, err := c.Apps().PetSets(api.NamespaceSystem).List(options) psList, err := c.Apps().StatefulSets(api.NamespaceSystem).List(options)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -46,15 +46,15 @@ import (
) )
const ( const (
petsetPoll = 10 * time.Second statefulsetPoll = 10 * time.Second
// Some pets install base packages via wget // Some pets install base packages via wget
petsetTimeout = 10 * time.Minute statefulsetTimeout = 10 * time.Minute
// Timeout for pet pods to change state // Timeout for pet pods to change state
petPodTimeout = 5 * time.Minute petPodTimeout = 5 * time.Minute
zookeeperManifestPath = "test/e2e/testing-manifests/petset/zookeeper" zookeeperManifestPath = "test/e2e/testing-manifests/petset/zookeeper"
mysqlGaleraManifestPath = "test/e2e/testing-manifests/petset/mysql-galera" mysqlGaleraManifestPath = "test/e2e/testing-manifests/petset/mysql-galera"
redisManifestPath = "test/e2e/testing-manifests/petset/redis" redisManifestPath = "test/e2e/testing-manifests/petset/redis"
// Should the test restart petset clusters? // Should the test restart statefulset clusters?
// TODO: enable when we've productionzed bringup of pets in this e2e. // TODO: enable when we've productionzed bringup of pets in this e2e.
restartCluster = false restartCluster = false
@ -65,7 +65,7 @@ const (
// Time: 25m, slow by design. // Time: 25m, slow by design.
// GCE Quota requirements: 3 pds, one per pet manifest declared above. // GCE Quota requirements: 3 pds, one per pet manifest declared above.
// GCE Api requirements: nodes and master need storage r/w permissions. // GCE Api requirements: nodes and master need storage r/w permissions.
var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() { var _ = framework.KubeDescribe("StatefulSet [Slow] [Feature:PetSet]", func() {
options := framework.FrameworkOptions{ options := framework.FrameworkOptions{
GroupVersion: &unversioned.GroupVersion{Group: apps.GroupName, Version: "v1alpha1"}, GroupVersion: &unversioned.GroupVersion{Group: apps.GroupName, Version: "v1alpha1"},
} }
@ -74,19 +74,19 @@ var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() {
var c clientset.Interface var c clientset.Interface
BeforeEach(func() { BeforeEach(func() {
// PetSet is in alpha, so it's disabled on some platforms. We skip this // StatefulSet is in alpha, so it's disabled on some platforms. We skip this
// test if a resource get fails on non-GCE platforms. // test if a resource get fails on non-GCE platforms.
// In theory, tests that restart pets should pass on any platform with a // In theory, tests that restart pets should pass on any platform with a
// dynamic volume provisioner. // dynamic volume provisioner.
if !framework.ProviderIs("gce") { if !framework.ProviderIs("gce") {
framework.SkipIfMissingResource(f.ClientPool, unversioned.GroupVersionResource{Group: apps.GroupName, Version: "v1alpha1", Resource: "petsets"}, f.Namespace.Name) framework.SkipIfMissingResource(f.ClientPool, unversioned.GroupVersionResource{Group: apps.GroupName, Version: "v1alpha1", Resource: "statefulsets"}, f.Namespace.Name)
} }
c = f.ClientSet c = f.ClientSet
ns = f.Namespace.Name ns = f.Namespace.Name
}) })
framework.KubeDescribe("Basic PetSet functionality", func() { framework.KubeDescribe("Basic StatefulSet functionality", func() {
psName := "pet" psName := "pet"
labels := map[string]string{ labels := map[string]string{
"foo": "bar", "foo": "bar",
@ -105,24 +105,24 @@ var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() {
if CurrentGinkgoTestDescription().Failed { if CurrentGinkgoTestDescription().Failed {
dumpDebugInfo(c, ns) dumpDebugInfo(c, ns)
} }
framework.Logf("Deleting all petset in ns %v", ns) framework.Logf("Deleting all statefulset in ns %v", ns)
deleteAllPetSets(c, ns) deleteAllStatefulSets(c, ns)
}) })
It("should provide basic identity [Feature:PetSet]", func() { It("should provide basic identity [Feature:StatefulSet]", func() {
By("creating petset " + psName + " in namespace " + ns) By("creating statefulset " + psName + " in namespace " + ns)
petMounts := []api.VolumeMount{{Name: "datadir", MountPath: "/data/"}} petMounts := []api.VolumeMount{{Name: "datadir", MountPath: "/data/"}}
podMounts := []api.VolumeMount{{Name: "home", MountPath: "/home"}} podMounts := []api.VolumeMount{{Name: "home", MountPath: "/home"}}
ps := newPetSet(psName, ns, headlessSvcName, 3, petMounts, podMounts, labels) ps := newStatefulSet(psName, ns, headlessSvcName, 3, petMounts, podMounts, labels)
_, err := c.Apps().PetSets(ns).Create(ps) _, err := c.Apps().StatefulSets(ns).Create(ps)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
pst := petSetTester{c: c} pst := statefulSetTester{c: c}
By("Saturating pet set " + ps.Name) By("Saturating pet set " + ps.Name)
pst.saturate(ps) pst.saturate(ps)
By("Verifying petset mounted data directory is usable") By("Verifying statefulset mounted data directory is usable")
ExpectNoError(pst.checkMount(ps, "/data")) ExpectNoError(pst.checkMount(ps, "/data"))
cmd := "echo $(hostname) > /data/hostname; sync;" cmd := "echo $(hostname) > /data/hostname; sync;"
@ -133,7 +133,7 @@ var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() {
pst.restart(ps) pst.restart(ps)
pst.saturate(ps) pst.saturate(ps)
By("Verifying petset mounted data directory is usable") By("Verifying statefulset mounted data directory is usable")
ExpectNoError(pst.checkMount(ps, "/data")) ExpectNoError(pst.checkMount(ps, "/data"))
cmd = "if [ \"$(cat /data/hostname)\" = \"$(hostname)\" ]; then exit 0; else exit 1; fi" cmd = "if [ \"$(cat /data/hostname)\" = \"$(hostname)\" ]; then exit 0; else exit 1; fi"
@ -142,15 +142,15 @@ var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() {
}) })
It("should handle healthy pet restarts during scale [Feature:PetSet]", func() { It("should handle healthy pet restarts during scale [Feature:PetSet]", func() {
By("creating petset " + psName + " in namespace " + ns) By("creating statefulset " + psName + " in namespace " + ns)
petMounts := []api.VolumeMount{{Name: "datadir", MountPath: "/data/"}} petMounts := []api.VolumeMount{{Name: "datadir", MountPath: "/data/"}}
podMounts := []api.VolumeMount{{Name: "home", MountPath: "/home"}} podMounts := []api.VolumeMount{{Name: "home", MountPath: "/home"}}
ps := newPetSet(psName, ns, headlessSvcName, 2, petMounts, podMounts, labels) ps := newStatefulSet(psName, ns, headlessSvcName, 2, petMounts, podMounts, labels)
_, err := c.Apps().PetSets(ns).Create(ps) _, err := c.Apps().StatefulSets(ns).Create(ps)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
pst := petSetTester{c: c} pst := statefulSetTester{c: c}
pst.waitForRunning(1, ps) pst.waitForRunning(1, ps)
@ -173,7 +173,7 @@ var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() {
By("Deleting unhealthy pet at index 1.") By("Deleting unhealthy pet at index 1.")
pst.deletePetAtIndex(1, ps) pst.deletePetAtIndex(1, ps)
By("Confirming all pets in petset are created.") By("Confirming all pets in statefulset are created.")
pst.saturate(ps) pst.saturate(ps)
}) })
}) })
@ -183,12 +183,12 @@ var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() {
if CurrentGinkgoTestDescription().Failed { if CurrentGinkgoTestDescription().Failed {
dumpDebugInfo(c, ns) dumpDebugInfo(c, ns)
} }
framework.Logf("Deleting all petset in ns %v", ns) framework.Logf("Deleting all statefulset in ns %v", ns)
deleteAllPetSets(c, ns) deleteAllStatefulSets(c, ns)
}) })
It("should creating a working zookeeper cluster [Feature:PetSet]", func() { It("should creating a working zookeeper cluster [Feature:PetSet]", func() {
pst := &petSetTester{c: c} pst := &statefulSetTester{c: c}
pet := &zookeeperTester{tester: pst} pet := &zookeeperTester{tester: pst}
By("Deploying " + pet.name()) By("Deploying " + pet.name())
ps := pet.deploy(ns) ps := pet.deploy(ns)
@ -209,7 +209,7 @@ var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() {
}) })
It("should creating a working redis cluster [Feature:PetSet]", func() { It("should creating a working redis cluster [Feature:PetSet]", func() {
pst := &petSetTester{c: c} pst := &statefulSetTester{c: c}
pet := &redisTester{tester: pst} pet := &redisTester{tester: pst}
By("Deploying " + pet.name()) By("Deploying " + pet.name())
ps := pet.deploy(ns) ps := pet.deploy(ns)
@ -230,7 +230,7 @@ var _ = framework.KubeDescribe("PetSet [Slow] [Feature:PetSet]", func() {
}) })
It("should creating a working mysql cluster [Feature:PetSet]", func() { It("should creating a working mysql cluster [Feature:PetSet]", func() {
pst := &petSetTester{c: c} pst := &statefulSetTester{c: c}
pet := &mysqlGaleraTester{tester: pst} pet := &mysqlGaleraTester{tester: pst}
By("Deploying " + pet.name()) By("Deploying " + pet.name())
ps := pet.deploy(ns) ps := pet.deploy(ns)
@ -263,7 +263,7 @@ var _ = framework.KubeDescribe("Pet set recreate [Slow] [Feature:PetSet]", func(
} }
headlessSvcName := "test" headlessSvcName := "test"
podName := "test-pod" podName := "test-pod"
petSetName := "web" statefulSetName := "web"
petPodName := "web-0" petPodName := "web-0"
BeforeEach(func() { BeforeEach(func() {
@ -280,11 +280,11 @@ var _ = framework.KubeDescribe("Pet set recreate [Slow] [Feature:PetSet]", func(
if CurrentGinkgoTestDescription().Failed { if CurrentGinkgoTestDescription().Failed {
dumpDebugInfo(c, ns) dumpDebugInfo(c, ns)
} }
By("Deleting all petset in ns " + ns) By("Deleting all statefulset in ns " + ns)
deleteAllPetSets(c, ns) deleteAllStatefulSets(c, ns)
}) })
It("should recreate evicted petset", func() { It("should recreate evicted statefulset", func() {
By("looking for a node to schedule pet set and pod") By("looking for a node to schedule pet set and pod")
nodes := framework.GetReadySchedulableNodesOrDie(f.ClientSet) nodes := framework.GetReadySchedulableNodesOrDie(f.ClientSet)
node := nodes.Items[0] node := nodes.Items[0]
@ -309,12 +309,12 @@ var _ = framework.KubeDescribe("Pet set recreate [Slow] [Feature:PetSet]", func(
pod, err := f.ClientSet.Core().Pods(f.Namespace.Name).Create(pod) pod, err := f.ClientSet.Core().Pods(f.Namespace.Name).Create(pod)
framework.ExpectNoError(err) framework.ExpectNoError(err)
By("creating petset with conflicting port in namespace " + f.Namespace.Name) By("creating statefulset with conflicting port in namespace " + f.Namespace.Name)
ps := newPetSet(petSetName, f.Namespace.Name, headlessSvcName, 1, nil, nil, labels) ps := newStatefulSet(statefulSetName, f.Namespace.Name, headlessSvcName, 1, nil, nil, labels)
petContainer := &ps.Spec.Template.Spec.Containers[0] petContainer := &ps.Spec.Template.Spec.Containers[0]
petContainer.Ports = append(petContainer.Ports, conflictingPort) petContainer.Ports = append(petContainer.Ports, conflictingPort)
ps.Spec.Template.Spec.NodeName = node.Name ps.Spec.Template.Spec.NodeName = node.Name
_, err = f.ClientSet.Apps().PetSets(f.Namespace.Name).Create(ps) _, err = f.ClientSet.Apps().StatefulSets(f.Namespace.Name).Create(ps)
framework.ExpectNoError(err) framework.ExpectNoError(err)
By("waiting until pod " + podName + " will start running in namespace " + f.Namespace.Name) By("waiting until pod " + podName + " will start running in namespace " + f.Namespace.Name)
@ -337,7 +337,7 @@ var _ = framework.KubeDescribe("Pet set recreate [Slow] [Feature:PetSet]", func(
} }
return true, nil return true, nil
} }
framework.Logf("Observed pet pod in namespace: %v, name: %v, uid: %v, status phase: %v. Waiting for petset controller to delete.", framework.Logf("Observed pet pod in namespace: %v, name: %v, uid: %v, status phase: %v. Waiting for statefulset controller to delete.",
pod.Namespace, pod.Name, pod.UID, pod.Status.Phase) pod.Namespace, pod.Name, pod.UID, pod.Status.Phase)
initialPetPodUID = pod.UID initialPetPodUID = pod.UID
return false, nil return false, nil
@ -391,23 +391,23 @@ func kubectlExecWithRetries(args ...string) (out string) {
} }
type petTester interface { type petTester interface {
deploy(ns string) *apps.PetSet deploy(ns string) *apps.StatefulSet
write(petIndex int, kv map[string]string) write(petIndex int, kv map[string]string)
read(petIndex int, key string) string read(petIndex int, key string) string
name() string name() string
} }
type zookeeperTester struct { type zookeeperTester struct {
ps *apps.PetSet ps *apps.StatefulSet
tester *petSetTester tester *statefulSetTester
} }
func (z *zookeeperTester) name() string { func (z *zookeeperTester) name() string {
return "zookeeper" return "zookeeper"
} }
func (z *zookeeperTester) deploy(ns string) *apps.PetSet { func (z *zookeeperTester) deploy(ns string) *apps.StatefulSet {
z.ps = z.tester.createPetSet(zookeeperManifestPath, ns) z.ps = z.tester.createStatefulSet(zookeeperManifestPath, ns)
return z.ps return z.ps
} }
@ -428,8 +428,8 @@ func (z *zookeeperTester) read(petIndex int, key string) string {
} }
type mysqlGaleraTester struct { type mysqlGaleraTester struct {
ps *apps.PetSet ps *apps.StatefulSet
tester *petSetTester tester *statefulSetTester
} }
func (m *mysqlGaleraTester) name() string { func (m *mysqlGaleraTester) name() string {
@ -444,13 +444,13 @@ func (m *mysqlGaleraTester) mysqlExec(cmd, ns, podName string) string {
return kubectlExecWithRetries(fmt.Sprintf("--namespace=%v", ns), "exec", podName, "--", "/bin/sh", "-c", cmd) return kubectlExecWithRetries(fmt.Sprintf("--namespace=%v", ns), "exec", podName, "--", "/bin/sh", "-c", cmd)
} }
func (m *mysqlGaleraTester) deploy(ns string) *apps.PetSet { func (m *mysqlGaleraTester) deploy(ns string) *apps.StatefulSet {
m.ps = m.tester.createPetSet(mysqlGaleraManifestPath, ns) m.ps = m.tester.createStatefulSet(mysqlGaleraManifestPath, ns)
framework.Logf("Deployed petset %v, initializing database", m.ps.Name) framework.Logf("Deployed statefulset %v, initializing database", m.ps.Name)
for _, cmd := range []string{ for _, cmd := range []string{
"create database petset;", "create database statefulset;",
"use petset; create table pet (k varchar(20), v varchar(20));", "use statefulset; create table pet (k varchar(20), v varchar(20));",
} { } {
framework.Logf(m.mysqlExec(cmd, ns, fmt.Sprintf("%v-0", m.ps.Name))) framework.Logf(m.mysqlExec(cmd, ns, fmt.Sprintf("%v-0", m.ps.Name)))
} }
@ -460,19 +460,19 @@ func (m *mysqlGaleraTester) deploy(ns string) *apps.PetSet {
func (m *mysqlGaleraTester) write(petIndex int, kv map[string]string) { func (m *mysqlGaleraTester) write(petIndex int, kv map[string]string) {
name := fmt.Sprintf("%v-%d", m.ps.Name, petIndex) name := fmt.Sprintf("%v-%d", m.ps.Name, petIndex)
for k, v := range kv { for k, v := range kv {
cmd := fmt.Sprintf("use petset; insert into pet (k, v) values (\"%v\", \"%v\");", k, v) cmd := fmt.Sprintf("use statefulset; insert into pet (k, v) values (\"%v\", \"%v\");", k, v)
framework.Logf(m.mysqlExec(cmd, m.ps.Namespace, name)) framework.Logf(m.mysqlExec(cmd, m.ps.Namespace, name))
} }
} }
func (m *mysqlGaleraTester) read(petIndex int, key string) string { func (m *mysqlGaleraTester) read(petIndex int, key string) string {
name := fmt.Sprintf("%v-%d", m.ps.Name, petIndex) name := fmt.Sprintf("%v-%d", m.ps.Name, petIndex)
return lastLine(m.mysqlExec(fmt.Sprintf("use petset; select v from pet where k=\"%v\";", key), m.ps.Namespace, name)) return lastLine(m.mysqlExec(fmt.Sprintf("use statefulset; select v from pet where k=\"%v\";", key), m.ps.Namespace, name))
} }
type redisTester struct { type redisTester struct {
ps *apps.PetSet ps *apps.StatefulSet
tester *petSetTester tester *statefulSetTester
} }
func (m *redisTester) name() string { func (m *redisTester) name() string {
@ -484,8 +484,8 @@ func (m *redisTester) redisExec(cmd, ns, podName string) string {
return framework.RunKubectlOrDie(fmt.Sprintf("--namespace=%v", ns), "exec", podName, "--", "/bin/sh", "-c", cmd) return framework.RunKubectlOrDie(fmt.Sprintf("--namespace=%v", ns), "exec", podName, "--", "/bin/sh", "-c", cmd)
} }
func (m *redisTester) deploy(ns string) *apps.PetSet { func (m *redisTester) deploy(ns string) *apps.StatefulSet {
m.ps = m.tester.createPetSet(redisManifestPath, ns) m.ps = m.tester.createStatefulSet(redisManifestPath, ns)
return m.ps return m.ps
} }
@ -506,9 +506,9 @@ func lastLine(out string) string {
return outLines[len(outLines)-1] return outLines[len(outLines)-1]
} }
func petSetFromManifest(fileName, ns string) *apps.PetSet { func statefulSetFromManifest(fileName, ns string) *apps.StatefulSet {
var ps apps.PetSet var ps apps.StatefulSet
framework.Logf("Parsing petset from %v", fileName) framework.Logf("Parsing statefulset from %v", fileName)
data, err := ioutil.ReadFile(fileName) data, err := ioutil.ReadFile(fileName)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
json, err := utilyaml.ToJSON(data) json, err := utilyaml.ToJSON(data)
@ -524,27 +524,27 @@ func petSetFromManifest(fileName, ns string) *apps.PetSet {
return &ps return &ps
} }
// petSetTester has all methods required to test a single petset. // statefulSetTester has all methods required to test a single statefulset.
type petSetTester struct { type statefulSetTester struct {
c clientset.Interface c clientset.Interface
} }
func (p *petSetTester) createPetSet(manifestPath, ns string) *apps.PetSet { func (p *statefulSetTester) createStatefulSet(manifestPath, ns string) *apps.StatefulSet {
mkpath := func(file string) string { mkpath := func(file string) string {
return filepath.Join(framework.TestContext.RepoRoot, manifestPath, file) return filepath.Join(framework.TestContext.RepoRoot, manifestPath, file)
} }
ps := petSetFromManifest(mkpath("petset.yaml"), ns) ps := statefulSetFromManifest(mkpath("petset.yaml"), ns)
framework.Logf(fmt.Sprintf("creating " + ps.Name + " service")) framework.Logf(fmt.Sprintf("creating " + ps.Name + " service"))
framework.RunKubectlOrDie("create", "-f", mkpath("service.yaml"), fmt.Sprintf("--namespace=%v", ns)) framework.RunKubectlOrDie("create", "-f", mkpath("service.yaml"), fmt.Sprintf("--namespace=%v", ns))
framework.Logf(fmt.Sprintf("creating petset %v/%v with %d replicas and selector %+v", ps.Namespace, ps.Name, ps.Spec.Replicas, ps.Spec.Selector)) framework.Logf(fmt.Sprintf("creating statefulset %v/%v with %d replicas and selector %+v", ps.Namespace, ps.Name, ps.Spec.Replicas, ps.Spec.Selector))
framework.RunKubectlOrDie("create", "-f", mkpath("petset.yaml"), fmt.Sprintf("--namespace=%v", ns)) framework.RunKubectlOrDie("create", "-f", mkpath("petset.yaml"), fmt.Sprintf("--namespace=%v", ns))
p.waitForRunning(ps.Spec.Replicas, ps) p.waitForRunning(ps.Spec.Replicas, ps)
return ps return ps
} }
func (p *petSetTester) checkMount(ps *apps.PetSet, mountPath string) error { func (p *statefulSetTester) checkMount(ps *apps.StatefulSet, mountPath string) error {
for _, cmd := range []string{ for _, cmd := range []string{
// Print inode, size etc // Print inode, size etc
fmt.Sprintf("ls -idlh %v", mountPath), fmt.Sprintf("ls -idlh %v", mountPath),
@ -560,7 +560,7 @@ func (p *petSetTester) checkMount(ps *apps.PetSet, mountPath string) error {
return nil return nil
} }
func (p *petSetTester) execInPets(ps *apps.PetSet, cmd string) error { func (p *statefulSetTester) execInPets(ps *apps.StatefulSet, cmd string) error {
podList := p.getPodList(ps) podList := p.getPodList(ps)
for _, pet := range podList.Items { for _, pet := range podList.Items {
stdout, err := framework.RunHostCmd(pet.Namespace, pet.Name, cmd) stdout, err := framework.RunHostCmd(pet.Namespace, pet.Name, cmd)
@ -572,7 +572,7 @@ func (p *petSetTester) execInPets(ps *apps.PetSet, cmd string) error {
return nil return nil
} }
func (p *petSetTester) saturate(ps *apps.PetSet) { func (p *statefulSetTester) saturate(ps *apps.StatefulSet) {
// TODO: Watch events and check that creation timestamps don't overlap // TODO: Watch events and check that creation timestamps don't overlap
var i int32 var i int32
for i = 0; i < ps.Spec.Replicas; i++ { for i = 0; i < ps.Spec.Replicas; i++ {
@ -583,23 +583,23 @@ func (p *petSetTester) saturate(ps *apps.PetSet) {
} }
} }
func (p *petSetTester) deletePetAtIndex(index int, ps *apps.PetSet) { func (p *statefulSetTester) deletePetAtIndex(index int, ps *apps.StatefulSet) {
// TODO: we won't use "-index" as the name strategy forever, // TODO: we won't use "-index" as the name strategy forever,
// pull the name out from an identity mapper. // pull the name out from an identity mapper.
name := fmt.Sprintf("%v-%v", ps.Name, index) name := fmt.Sprintf("%v-%v", ps.Name, index)
noGrace := int64(0) noGrace := int64(0)
if err := p.c.Core().Pods(ps.Namespace).Delete(name, &api.DeleteOptions{GracePeriodSeconds: &noGrace}); err != nil { if err := p.c.Core().Pods(ps.Namespace).Delete(name, &api.DeleteOptions{GracePeriodSeconds: &noGrace}); err != nil {
framework.Failf("Failed to delete pet %v for PetSet %v: %v", name, ps.Name, ps.Namespace, err) framework.Failf("Failed to delete pet %v for StatefulSet %v: %v", name, ps.Name, ps.Namespace, err)
} }
} }
func (p *petSetTester) scale(ps *apps.PetSet, count int32) error { func (p *statefulSetTester) scale(ps *apps.StatefulSet, count int32) error {
name := ps.Name name := ps.Name
ns := ps.Namespace ns := ps.Namespace
p.update(ns, name, func(ps *apps.PetSet) { ps.Spec.Replicas = count }) p.update(ns, name, func(ps *apps.StatefulSet) { ps.Spec.Replicas = count })
var petList *api.PodList var petList *api.PodList
pollErr := wait.PollImmediate(petsetPoll, petsetTimeout, func() (bool, error) { pollErr := wait.PollImmediate(statefulsetPoll, statefulsetTimeout, func() (bool, error) {
petList = p.getPodList(ps) petList = p.getPodList(ps)
if int32(len(petList.Items)) == count { if int32(len(petList.Items)) == count {
return true, nil return true, nil
@ -614,36 +614,36 @@ func (p *petSetTester) scale(ps *apps.PetSet, count int32) error {
unhealthy = append(unhealthy, fmt.Sprintf("%v: deletion %v, phase %v, readiness %v", pet.Name, delTs, phase, readiness)) unhealthy = append(unhealthy, fmt.Sprintf("%v: deletion %v, phase %v, readiness %v", pet.Name, delTs, phase, readiness))
} }
} }
return fmt.Errorf("Failed to scale petset to %d in %v. Remaining pods:\n%v", count, petsetTimeout, unhealthy) return fmt.Errorf("Failed to scale statefulset to %d in %v. Remaining pods:\n%v", count, statefulsetTimeout, unhealthy)
} }
return nil return nil
} }
func (p *petSetTester) restart(ps *apps.PetSet) { func (p *statefulSetTester) restart(ps *apps.StatefulSet) {
oldReplicas := ps.Spec.Replicas oldReplicas := ps.Spec.Replicas
ExpectNoError(p.scale(ps, 0)) ExpectNoError(p.scale(ps, 0))
p.update(ps.Namespace, ps.Name, func(ps *apps.PetSet) { ps.Spec.Replicas = oldReplicas }) p.update(ps.Namespace, ps.Name, func(ps *apps.StatefulSet) { ps.Spec.Replicas = oldReplicas })
} }
func (p *petSetTester) update(ns, name string, update func(ps *apps.PetSet)) { func (p *statefulSetTester) update(ns, name string, update func(ps *apps.StatefulSet)) {
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
ps, err := p.c.Apps().PetSets(ns).Get(name) ps, err := p.c.Apps().StatefulSets(ns).Get(name)
if err != nil { if err != nil {
framework.Failf("failed to get petset %q: %v", name, err) framework.Failf("failed to get statefulset %q: %v", name, err)
} }
update(ps) update(ps)
ps, err = p.c.Apps().PetSets(ns).Update(ps) ps, err = p.c.Apps().StatefulSets(ns).Update(ps)
if err == nil { if err == nil {
return return
} }
if !apierrs.IsConflict(err) && !apierrs.IsServerTimeout(err) { if !apierrs.IsConflict(err) && !apierrs.IsServerTimeout(err) {
framework.Failf("failed to update petset %q: %v", name, err) framework.Failf("failed to update statefulset %q: %v", name, err)
} }
} }
framework.Failf("too many retries draining petset %q", name) framework.Failf("too many retries draining statefulset %q", name)
} }
func (p *petSetTester) getPodList(ps *apps.PetSet) *api.PodList { func (p *statefulSetTester) getPodList(ps *apps.StatefulSet) *api.PodList {
selector, err := unversioned.LabelSelectorAsSelector(ps.Spec.Selector) selector, err := unversioned.LabelSelectorAsSelector(ps.Spec.Selector)
ExpectNoError(err) ExpectNoError(err)
podList, err := p.c.Core().Pods(ps.Namespace).List(api.ListOptions{LabelSelector: selector}) podList, err := p.c.Core().Pods(ps.Namespace).List(api.ListOptions{LabelSelector: selector})
@ -651,22 +651,22 @@ func (p *petSetTester) getPodList(ps *apps.PetSet) *api.PodList {
return podList return podList
} }
func (p *petSetTester) confirmPetCount(count int, ps *apps.PetSet, timeout time.Duration) { func (p *statefulSetTester) confirmPetCount(count int, ps *apps.StatefulSet, timeout time.Duration) {
start := time.Now() start := time.Now()
deadline := start.Add(timeout) deadline := start.Add(timeout)
for t := time.Now(); t.Before(deadline); t = time.Now() { for t := time.Now(); t.Before(deadline); t = time.Now() {
podList := p.getPodList(ps) podList := p.getPodList(ps)
petCount := len(podList.Items) petCount := len(podList.Items)
if petCount != count { if petCount != count {
framework.Failf("PetSet %v scaled unexpectedly scaled to %d -> %d replicas: %+v", ps.Name, count, len(podList.Items), podList) framework.Failf("StatefulSet %v scaled unexpectedly scaled to %d -> %d replicas: %+v", ps.Name, count, len(podList.Items), podList)
} }
framework.Logf("Verifying petset %v doesn't scale past %d for another %+v", ps.Name, count, deadline.Sub(t)) framework.Logf("Verifying statefulset %v doesn't scale past %d for another %+v", ps.Name, count, deadline.Sub(t))
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
} }
func (p *petSetTester) waitForRunning(numPets int32, ps *apps.PetSet) { func (p *statefulSetTester) waitForRunning(numPets int32, ps *apps.StatefulSet) {
pollErr := wait.PollImmediate(petsetPoll, petsetTimeout, pollErr := wait.PollImmediate(statefulsetPoll, statefulsetTimeout,
func() (bool, error) { func() (bool, error) {
podList := p.getPodList(ps) podList := p.getPodList(ps)
if int32(len(podList.Items)) < numPets { if int32(len(podList.Items)) < numPets {
@ -692,7 +692,7 @@ func (p *petSetTester) waitForRunning(numPets int32, ps *apps.PetSet) {
p.waitForStatus(ps, numPets) p.waitForStatus(ps, numPets)
} }
func (p *petSetTester) setHealthy(ps *apps.PetSet) { func (p *statefulSetTester) setHealthy(ps *apps.StatefulSet) {
podList := p.getPodList(ps) podList := p.getPodList(ps)
markedHealthyPod := "" markedHealthyPod := ""
for _, pod := range podList.Items { for _, pod := range podList.Items {
@ -706,21 +706,21 @@ func (p *petSetTester) setHealthy(ps *apps.PetSet) {
framework.Failf("Found multiple non-healthy pets: %v and %v", pod.Name, markedHealthyPod) framework.Failf("Found multiple non-healthy pets: %v and %v", pod.Name, markedHealthyPod)
} }
p, err := framework.UpdatePodWithRetries(p.c, pod.Namespace, pod.Name, func(up *api.Pod) { p, err := framework.UpdatePodWithRetries(p.c, pod.Namespace, pod.Name, func(up *api.Pod) {
up.Annotations[petset.PetSetInitAnnotation] = "true" up.Annotations[petset.StatefulSetInitAnnotation] = "true"
}) })
ExpectNoError(err) ExpectNoError(err)
framework.Logf("Set annotation %v to %v on pod %v", petset.PetSetInitAnnotation, p.Annotations[petset.PetSetInitAnnotation], pod.Name) framework.Logf("Set annotation %v to %v on pod %v", petset.StatefulSetInitAnnotation, p.Annotations[petset.StatefulSetInitAnnotation], pod.Name)
markedHealthyPod = pod.Name markedHealthyPod = pod.Name
} }
} }
func (p *petSetTester) waitForStatus(ps *apps.PetSet, expectedReplicas int32) { func (p *statefulSetTester) waitForStatus(ps *apps.StatefulSet, expectedReplicas int32) {
framework.Logf("Waiting for petset status.replicas updated to %d", expectedReplicas) framework.Logf("Waiting for statefulset status.replicas updated to %d", expectedReplicas)
ns, name := ps.Namespace, ps.Name ns, name := ps.Namespace, ps.Name
pollErr := wait.PollImmediate(petsetPoll, petsetTimeout, pollErr := wait.PollImmediate(statefulsetPoll, statefulsetTimeout,
func() (bool, error) { func() (bool, error) {
psGet, err := p.c.Apps().PetSets(ns).Get(name) psGet, err := p.c.Apps().StatefulSets(ns).Get(name)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -735,30 +735,30 @@ func (p *petSetTester) waitForStatus(ps *apps.PetSet, expectedReplicas int32) {
} }
} }
func deleteAllPetSets(c clientset.Interface, ns string) { func deleteAllStatefulSets(c clientset.Interface, ns string) {
pst := &petSetTester{c: c} pst := &statefulSetTester{c: c}
psList, err := c.Apps().PetSets(ns).List(api.ListOptions{LabelSelector: labels.Everything()}) psList, err := c.Apps().StatefulSets(ns).List(api.ListOptions{LabelSelector: labels.Everything()})
ExpectNoError(err) ExpectNoError(err)
// Scale down each petset, then delete it completely. // Scale down each statefulset, then delete it completely.
// Deleting a pvc without doing this will leak volumes, #25101. // Deleting a pvc without doing this will leak volumes, #25101.
errList := []string{} errList := []string{}
for _, ps := range psList.Items { for _, ps := range psList.Items {
framework.Logf("Scaling petset %v to 0", ps.Name) framework.Logf("Scaling statefulset %v to 0", ps.Name)
if err := pst.scale(&ps, 0); err != nil { if err := pst.scale(&ps, 0); err != nil {
errList = append(errList, fmt.Sprintf("%v", err)) errList = append(errList, fmt.Sprintf("%v", err))
} }
pst.waitForStatus(&ps, 0) pst.waitForStatus(&ps, 0)
framework.Logf("Deleting petset %v", ps.Name) framework.Logf("Deleting statefulset %v", ps.Name)
if err := c.Apps().PetSets(ps.Namespace).Delete(ps.Name, nil); err != nil { if err := c.Apps().StatefulSets(ps.Namespace).Delete(ps.Name, nil); err != nil {
errList = append(errList, fmt.Sprintf("%v", err)) errList = append(errList, fmt.Sprintf("%v", err))
} }
} }
// pvs are global, so we need to wait for the exact ones bound to the petset pvcs. // pvs are global, so we need to wait for the exact ones bound to the statefulset pvcs.
pvNames := sets.NewString() pvNames := sets.NewString()
// TODO: Don't assume all pvcs in the ns belong to a petset // TODO: Don't assume all pvcs in the ns belong to a statefulset
pvcPollErr := wait.PollImmediate(petsetPoll, petsetTimeout, func() (bool, error) { pvcPollErr := wait.PollImmediate(statefulsetPoll, statefulsetTimeout, func() (bool, error) {
pvcList, err := c.Core().PersistentVolumeClaims(ns).List(api.ListOptions{LabelSelector: labels.Everything()}) pvcList, err := c.Core().PersistentVolumeClaims(ns).List(api.ListOptions{LabelSelector: labels.Everything()})
if err != nil { if err != nil {
framework.Logf("WARNING: Failed to list pvcs, retrying %v", err) framework.Logf("WARNING: Failed to list pvcs, retrying %v", err)
@ -778,7 +778,7 @@ func deleteAllPetSets(c clientset.Interface, ns string) {
errList = append(errList, fmt.Sprintf("Timeout waiting for pvc deletion.")) errList = append(errList, fmt.Sprintf("Timeout waiting for pvc deletion."))
} }
pollErr := wait.PollImmediate(petsetPoll, petsetTimeout, func() (bool, error) { pollErr := wait.PollImmediate(statefulsetPoll, statefulsetTimeout, func() (bool, error) {
pvList, err := c.Core().PersistentVolumes().List(api.ListOptions{LabelSelector: labels.Everything()}) pvList, err := c.Core().PersistentVolumes().List(api.ListOptions{LabelSelector: labels.Everything()})
if err != nil { if err != nil {
framework.Logf("WARNING: Failed to list pvs, retrying %v", err) framework.Logf("WARNING: Failed to list pvs, retrying %v", err)
@ -793,7 +793,7 @@ func deleteAllPetSets(c clientset.Interface, ns string) {
if len(waitingFor) == 0 { if len(waitingFor) == 0 {
return true, nil return true, nil
} }
framework.Logf("Still waiting for pvs of petset to disappear:\n%v", strings.Join(waitingFor, "\n")) framework.Logf("Still waiting for pvs of statefulset to disappear:\n%v", strings.Join(waitingFor, "\n"))
return false, nil return false, nil
}) })
if pollErr != nil { if pollErr != nil {
@ -826,13 +826,13 @@ func pollReadWithTimeout(pet petTester, petNumber int, key, expectedVal string)
} }
func isInitialized(pod api.Pod) bool { func isInitialized(pod api.Pod) bool {
initialized, ok := pod.Annotations[petset.PetSetInitAnnotation] initialized, ok := pod.Annotations[petset.StatefulSetInitAnnotation]
if !ok { if !ok {
return false return false
} }
inited, err := strconv.ParseBool(initialized) inited, err := strconv.ParseBool(initialized)
if err != nil { if err != nil {
framework.Failf("Couldn't parse petset init annotations %v", initialized) framework.Failf("Couldn't parse statefulset init annotations %v", initialized)
} }
return inited return inited
} }
@ -862,7 +862,7 @@ func newPVC(name string) api.PersistentVolumeClaim {
} }
} }
func newPetSet(name, ns, governingSvcName string, replicas int32, petMounts []api.VolumeMount, podMounts []api.VolumeMount, labels map[string]string) *apps.PetSet { func newStatefulSet(name, ns, governingSvcName string, replicas int32, petMounts []api.VolumeMount, podMounts []api.VolumeMount, labels map[string]string) *apps.StatefulSet {
mounts := append(petMounts, podMounts...) mounts := append(petMounts, podMounts...)
claims := []api.PersistentVolumeClaim{} claims := []api.PersistentVolumeClaim{}
for _, m := range petMounts { for _, m := range petMounts {
@ -881,16 +881,16 @@ func newPetSet(name, ns, governingSvcName string, replicas int32, petMounts []ap
}) })
} }
return &apps.PetSet{ return &apps.StatefulSet{
TypeMeta: unversioned.TypeMeta{ TypeMeta: unversioned.TypeMeta{
Kind: "PetSet", Kind: "StatefulSet",
APIVersion: "apps/v1beta1", APIVersion: "apps/v1beta1",
}, },
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: name, Name: name,
Namespace: ns, Namespace: ns,
}, },
Spec: apps.PetSetSpec{ Spec: apps.StatefulSetSpec{
Selector: &unversioned.LabelSelector{ Selector: &unversioned.LabelSelector{
MatchLabels: labels, MatchLabels: labels,
}, },

View File

@ -1,5 +1,5 @@
apiVersion: apps/v1alpha1 apiVersion: apps/v1alpha1
kind: PetSet kind: StatefulSet
metadata: metadata:
name: mysql name: mysql
spec: spec:

View File

@ -1,5 +1,5 @@
apiVersion: apps/v1alpha1 apiVersion: apps/v1alpha1
kind: PetSet kind: StatefulSet
metadata: metadata:
name: rd name: rd
spec: spec:

View File

@ -1,5 +1,5 @@
apiVersion: apps/v1alpha1 apiVersion: apps/v1alpha1
kind: PetSet kind: StatefulSet
metadata: metadata:
name: zoo name: zoo
spec: spec: