Commit f6a186b1 authored by Andy Goldstein's avatar Andy Goldstein

Switch statefulset controller to shared informers

parent eef16cf1
...@@ -29,11 +29,10 @@ func startStatefulSetController(ctx ControllerContext) (bool, error) { ...@@ -29,11 +29,10 @@ func startStatefulSetController(ctx ControllerContext) (bool, error) {
if !ctx.AvailableResources[schema.GroupVersionResource{Group: "apps", Version: "v1beta1", Resource: "statefulsets"}] { if !ctx.AvailableResources[schema.GroupVersionResource{Group: "apps", Version: "v1beta1", Resource: "statefulsets"}] {
return false, nil return false, nil
} }
resyncPeriod := ResyncPeriod(&ctx.Options)()
go statefulset.NewStatefulSetController( go statefulset.NewStatefulSetController(
ctx.InformerFactory.Pods().Informer(), ctx.NewInformerFactory.Core().V1().Pods(),
ctx.NewInformerFactory.Apps().V1beta1().StatefulSets(),
ctx.ClientBuilder.ClientOrDie("statefulset-controller"), ctx.ClientBuilder.ClientOrDie("statefulset-controller"),
resyncPeriod,
).Run(1, ctx.Stop) ).Run(1, ctx.Stop)
return true, nil return true, nil
} }
...@@ -23,17 +23,20 @@ go_library( ...@@ -23,17 +23,20 @@ go_library(
"//pkg/api/v1/pod:go_default_library", "//pkg/api/v1/pod:go_default_library",
"//pkg/apis/apps/v1beta1:go_default_library", "//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library", "//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/legacylisters:go_default_library", "//pkg/client/informers/informers_generated/externalversions/apps/v1beta1:go_default_library",
"//pkg/client/informers/informers_generated/externalversions/core/v1:go_default_library",
"//pkg/client/listers/apps/v1beta1:go_default_library",
"//pkg/client/listers/core/v1:go_default_library",
"//pkg/client/retry:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//vendor:github.com/golang/glog", "//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/api/errors", "//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/errors", "//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/runtime", "//vendor:k8s.io/apimachinery/pkg/util/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/wait", "//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/watch",
"//vendor:k8s.io/client-go/kubernetes/typed/core/v1", "//vendor:k8s.io/client-go/kubernetes/typed/core/v1",
"//vendor:k8s.io/client-go/pkg/api",
"//vendor:k8s.io/client-go/pkg/api/v1", "//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/tools/cache", "//vendor:k8s.io/client-go/tools/cache",
"//vendor:k8s.io/client-go/tools/record", "//vendor:k8s.io/client-go/tools/record",
...@@ -56,17 +59,22 @@ go_test( ...@@ -56,17 +59,22 @@ go_test(
"//pkg/api/v1/pod:go_default_library", "//pkg/api/v1/pod:go_default_library",
"//pkg/apis/apps/v1beta1:go_default_library", "//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/client/clientset_generated/clientset/fake:go_default_library", "//pkg/client/clientset_generated/clientset/fake:go_default_library",
"//pkg/client/informers/informers_generated/externalversions:go_default_library",
"//pkg/client/informers/informers_generated/externalversions/apps/v1beta1:go_default_library",
"//pkg/client/informers/informers_generated/externalversions/core/v1:go_default_library",
"//pkg/client/legacylisters:go_default_library", "//pkg/client/legacylisters:go_default_library",
"//pkg/client/listers/apps/v1beta1:go_default_library",
"//pkg/client/listers/core/v1:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/api/errors", "//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/api/resource", "//vendor:k8s.io/apimachinery/pkg/api/resource",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/client-go/testing", "//vendor:k8s.io/client-go/testing",
"//vendor:k8s.io/client-go/tools/cache", "//vendor:k8s.io/client-go/tools/cache",
"//vendor:k8s.io/client-go/tools/record", "//vendor:k8s.io/client-go/tools/record",
"//vendor:k8s.io/client-go/util/workqueue",
], ],
) )
......
...@@ -23,11 +23,15 @@ import ( ...@@ -23,11 +23,15 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
errorutils "k8s.io/apimachinery/pkg/util/errors" errorutils "k8s.io/apimachinery/pkg/util/errors"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/pkg/api"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
appslisters "k8s.io/kubernetes/pkg/client/listers/apps/v1beta1"
corelisters "k8s.io/kubernetes/pkg/client/listers/core/v1"
"k8s.io/kubernetes/pkg/client/retry"
) )
// StatefulPodControlInterface defines the interface that StatefulSetController uses to create, update, and delete Pods, // StatefulPodControlInterface defines the interface that StatefulSetController uses to create, update, and delete Pods,
...@@ -52,15 +56,17 @@ type StatefulPodControlInterface interface { ...@@ -52,15 +56,17 @@ type StatefulPodControlInterface interface {
UpdateStatefulSetReplicas(set *apps.StatefulSet, replicas int32) error UpdateStatefulSetReplicas(set *apps.StatefulSet, replicas int32) error
} }
func NewRealStatefulPodControl(client clientset.Interface, recorder record.EventRecorder) StatefulPodControlInterface { func NewRealStatefulPodControl(client clientset.Interface, setLister appslisters.StatefulSetLister, podLister corelisters.PodLister, recorder record.EventRecorder) StatefulPodControlInterface {
return &realStatefulPodControl{client, recorder} return &realStatefulPodControl{client, setLister, podLister, recorder}
} }
// realStatefulPodControl implements StatefulPodControlInterface using a clientset.Interface to communicate with the // realStatefulPodControl implements StatefulPodControlInterface using a clientset.Interface to communicate with the
// API server. The struct is package private as the internal details are irrelevant to importing packages. // API server. The struct is package private as the internal details are irrelevant to importing packages.
type realStatefulPodControl struct { type realStatefulPodControl struct {
client clientset.Interface client clientset.Interface
recorder record.EventRecorder setLister appslisters.StatefulSetLister
podLister corelisters.PodLister
recorder record.EventRecorder
} }
func (spc *realStatefulPodControl) CreateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error { func (spc *realStatefulPodControl) CreateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error {
...@@ -80,54 +86,56 @@ func (spc *realStatefulPodControl) CreateStatefulPod(set *apps.StatefulSet, pod ...@@ -80,54 +86,56 @@ func (spc *realStatefulPodControl) CreateStatefulPod(set *apps.StatefulSet, pod
} }
func (spc *realStatefulPodControl) UpdateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error { func (spc *realStatefulPodControl) UpdateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error {
// we make a copy of the Pod on the stack and mutate the copy attemptedUpdate := false
// we copy back to pod to notify the caller of successful mutation
obj, err := api.Scheme.Copy(pod) err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
if err != nil {
return fmt.Errorf("unable to copy pod: %v", err)
}
podCopy := obj.(*v1.Pod)
for attempt := 0; attempt < maxUpdateRetries; attempt++ {
// assume the Pod is consistent // assume the Pod is consistent
consistent := true consistent := true
// if the Pod does not conform to it's identity, update the identity and dirty the Pod // if the Pod does not conform to its identity, update the identity and dirty the Pod
if !identityMatches(set, podCopy) { if !identityMatches(set, pod) {
updateIdentity(set, podCopy) updateIdentity(set, pod)
consistent = false consistent = false
} }
// if the Pod does not conform to the StatefulSet's storage requirements, update the Pod's PVC's, // if the Pod does not conform to the StatefulSet's storage requirements, update the Pod's PVC's,
// dirty the Pod, and create any missing PVCs // dirty the Pod, and create any missing PVCs
if !storageMatches(set, podCopy) { if !storageMatches(set, pod) {
updateStorage(set, podCopy) updateStorage(set, pod)
consistent = false consistent = false
if err := spc.createPersistentVolumeClaims(set, podCopy); err != nil { if err := spc.createPersistentVolumeClaims(set, pod); err != nil {
spc.recordPodEvent("update", set, pod, err) spc.recordPodEvent("update", set, pod, err)
return err return err
} }
} }
// if the Pod is not dirty do nothing // if the Pod is not dirty do nothing
if consistent { if consistent {
*pod = *podCopy
return nil return nil
} }
attemptedUpdate = true
// commit the update, retrying on conflicts // commit the update, retrying on conflicts
_, err = spc.client.Core().Pods(set.Namespace).Update(podCopy) _, err := spc.client.Core().Pods(set.Namespace).Update(pod)
if !apierrors.IsConflict(err) { if err == nil {
if err == nil { return nil
*pod = *podCopy
}
spc.recordPodEvent("update", set, pod, err)
return err
} }
conflicting, err := spc.client.Core().Pods(set.Namespace).Get(podCopy.Name, metav1.GetOptions{}) updateErr := err
if err != nil {
spc.recordPodEvent("update", set, podCopy, err) if updated, err := spc.podLister.Pods(set.Namespace).Get(pod.Name); err == nil {
return err // make a copy so we don't mutate the shared cache
if copy, err := api.Scheme.DeepCopy(updated); err == nil {
pod = copy.(*v1.Pod)
} else {
utilruntime.HandleError(fmt.Errorf("error copying updated Pod: %v", err))
}
} else {
utilruntime.HandleError(fmt.Errorf("error getting updated Pod %s/%s from lister: %v", set.Namespace, pod.Name, err))
} }
*podCopy = *conflicting
return updateErr
})
if attemptedUpdate {
spc.recordPodEvent("update", set, pod, err)
} }
spc.recordPodEvent("update", set, pod, updateConflictError) return err
return updateConflictError
} }
func (spc *realStatefulPodControl) DeleteStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error { func (spc *realStatefulPodControl) DeleteStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error {
...@@ -137,31 +145,28 @@ func (spc *realStatefulPodControl) DeleteStatefulPod(set *apps.StatefulSet, pod ...@@ -137,31 +145,28 @@ func (spc *realStatefulPodControl) DeleteStatefulPod(set *apps.StatefulSet, pod
} }
func (spc *realStatefulPodControl) UpdateStatefulSetReplicas(set *apps.StatefulSet, replicas int32) error { func (spc *realStatefulPodControl) UpdateStatefulSetReplicas(set *apps.StatefulSet, replicas int32) error {
if set.Status.Replicas == replicas { return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
return nil set.Status.Replicas = replicas
} _, err := spc.client.Apps().StatefulSets(set.Namespace).UpdateStatus(set)
obj, err := api.Scheme.Copy(set) if err == nil {
if err != nil { return nil
return fmt.Errorf("unable to copy set: %v", err)
}
setCopy := obj.(*apps.StatefulSet)
setCopy.Status.Replicas = replicas
for attempt := 0; attempt < maxUpdateRetries; attempt++ {
_, err := spc.client.Apps().StatefulSets(setCopy.Namespace).UpdateStatus(setCopy)
if !apierrors.IsConflict(err) {
if err == nil {
*set = *setCopy
}
return err
} }
conflicting, err := spc.client.Apps().StatefulSets(setCopy.Namespace).Get(setCopy.Name, metav1.GetOptions{})
if err != nil { updateErr := err
return err
if updated, err := spc.setLister.StatefulSets(set.Namespace).Get(set.Name); err == nil {
// make a copy so we don't mutate the shared cache
if copy, err := api.Scheme.DeepCopy(updated); err == nil {
set = copy.(*apps.StatefulSet)
} else {
utilruntime.HandleError(fmt.Errorf("error copying updated StatefulSet: %v", err))
}
} else {
utilruntime.HandleError(fmt.Errorf("error getting updated StatefulSet %s/%s from lister: %v", set.Namespace, set.Name, err))
} }
conflicting.Status.Replicas = setCopy.Status.Replicas
*setCopy = *conflicting return updateErr
} })
return updateConflictError
} }
// recordPodEvent records an event for verb applied to a Pod in a StatefulSet. If err is nil the generated event will // recordPodEvent records an event for verb applied to a Pod in a StatefulSet. If err is nil the generated event will
......
...@@ -22,23 +22,23 @@ import ( ...@@ -22,23 +22,23 @@ import (
"sort" "sort"
"time" "time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1"
clientv1 "k8s.io/client-go/pkg/api/v1" clientv1 "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/pkg/client/legacylisters" appsinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions/apps/v1beta1"
coreinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions/core/v1"
"k8s.io/apimachinery/pkg/runtime" appslisters "k8s.io/kubernetes/pkg/client/listers/apps/v1beta1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" corelisters "k8s.io/kubernetes/pkg/client/listers/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"github.com/golang/glog" "github.com/golang/glog"
...@@ -56,20 +56,24 @@ type StatefulSetController struct { ...@@ -56,20 +56,24 @@ type StatefulSetController struct {
// control returns an interface capable of syncing a stateful set. // control returns an interface capable of syncing a stateful set.
// Abstracted out for testing. // Abstracted out for testing.
control StatefulSetControlInterface control StatefulSetControlInterface
// podStore is a cache of watched pods. // podLister is able to list/get pods from a shared informer's store
podStore listers.StoreToPodLister podLister corelisters.PodLister
// podStoreSynced returns true if the pod store has synced at least once. // podListerSynced returns true if the pod shared informer has synced at least once
podStoreSynced cache.InformerSynced podListerSynced cache.InformerSynced
// A store of StatefulSets, populated by setController. // setLister is able to list/get stateful sets from a shared informer's store
setStore listers.StoreToStatefulSetLister setLister appslisters.StatefulSetLister
// Watches changes to all StatefulSets. // setListerSynced returns true if the stateful set shared informer has synced at least once
setController cache.Controller setListerSynced cache.InformerSynced
// StatefulSets that need to be synced. // StatefulSets that need to be synced.
queue workqueue.RateLimitingInterface queue workqueue.RateLimitingInterface
} }
// NewStatefulSetController creates a new statefulset controller. // NewStatefulSetController creates a new statefulset controller.
func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient clientset.Interface, resyncPeriod time.Duration) *StatefulSetController { func NewStatefulSetController(
podInformer coreinformers.PodInformer,
setInformer appsinformers.StatefulSetInformer,
kubeClient clientset.Interface,
) *StatefulSetController {
eventBroadcaster := record.NewBroadcaster() eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartLogging(glog.Infof)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")}) eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.Core().RESTClient()).Events("")})
...@@ -77,11 +81,11 @@ func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient ...@@ -77,11 +81,11 @@ func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient
ssc := &StatefulSetController{ ssc := &StatefulSetController{
kubeClient: kubeClient, kubeClient: kubeClient,
control: NewDefaultStatefulSetControl(NewRealStatefulPodControl(kubeClient, recorder)), control: NewDefaultStatefulSetControl(NewRealStatefulPodControl(kubeClient, setInformer.Lister(), podInformer.Lister(), recorder)),
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "statefulset"), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "statefulset"),
} }
podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
// lookup the statefulset and enqueue // lookup the statefulset and enqueue
AddFunc: ssc.addPod, AddFunc: ssc.addPod,
// lookup current and old statefulset if labels changed // lookup current and old statefulset if labels changed
...@@ -89,19 +93,10 @@ func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient ...@@ -89,19 +93,10 @@ func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient
// lookup statefulset accounting for deletion tombstones // lookup statefulset accounting for deletion tombstones
DeleteFunc: ssc.deletePod, DeleteFunc: ssc.deletePod,
}) })
ssc.podStore.Indexer = podInformer.GetIndexer() ssc.podLister = podInformer.Lister()
ssc.podListerSynced = podInformer.Informer().HasSynced
ssc.setStore.Store, ssc.setController = cache.NewInformer( setInformer.Informer().AddEventHandlerWithResyncPeriod(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
return ssc.kubeClient.Apps().StatefulSets(v1.NamespaceAll).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return ssc.kubeClient.Apps().StatefulSets(v1.NamespaceAll).Watch(options)
},
},
&apps.StatefulSet{},
statefulSetResyncPeriod,
cache.ResourceEventHandlerFuncs{ cache.ResourceEventHandlerFuncs{
AddFunc: ssc.enqueueStatefulSet, AddFunc: ssc.enqueueStatefulSet,
UpdateFunc: func(old, cur interface{}) { UpdateFunc: func(old, cur interface{}) {
...@@ -114,9 +109,12 @@ func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient ...@@ -114,9 +109,12 @@ func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient
}, },
DeleteFunc: ssc.enqueueStatefulSet, DeleteFunc: ssc.enqueueStatefulSet,
}, },
statefulSetResyncPeriod,
) )
ssc.setLister = setInformer.Lister()
ssc.setListerSynced = setInformer.Informer().HasSynced
// TODO: Watch volumes // TODO: Watch volumes
ssc.podStoreSynced = podInformer.GetController().HasSynced
return ssc return ssc
} }
...@@ -124,18 +122,20 @@ func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient ...@@ -124,18 +122,20 @@ func NewStatefulSetController(podInformer cache.SharedIndexInformer, kubeClient
func (ssc *StatefulSetController) Run(workers int, stopCh <-chan struct{}) { func (ssc *StatefulSetController) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash() defer utilruntime.HandleCrash()
defer ssc.queue.ShutDown() defer ssc.queue.ShutDown()
glog.Infof("Starting statefulset controller") glog.Infof("Starting statefulset controller")
if !cache.WaitForCacheSync(stopCh, ssc.podStoreSynced) {
if !cache.WaitForCacheSync(stopCh, ssc.podListerSynced, ssc.setListerSynced) {
utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
return return
} }
go ssc.setController.Run(stopCh)
for i := 0; i < workers; i++ { for i := 0; i < workers; i++ {
go wait.Until(ssc.worker, time.Second, stopCh) go wait.Until(ssc.worker, time.Second, stopCh)
} }
<-stopCh <-stopCh
glog.Infof("Shutting down statefulset controller") glog.Infof("Shutting down statefulset controller")
} }
// addPod adds the statefulset for the pod to the sync queue // addPod adds the statefulset for the pod to the sync queue
...@@ -204,12 +204,12 @@ func (ssc *StatefulSetController) getPodsForStatefulSet(set *apps.StatefulSet) ( ...@@ -204,12 +204,12 @@ func (ssc *StatefulSetController) getPodsForStatefulSet(set *apps.StatefulSet) (
if err != nil { if err != nil {
return []*v1.Pod{}, err return []*v1.Pod{}, err
} }
return ssc.podStore.Pods(set.Namespace).List(sel) return ssc.podLister.Pods(set.Namespace).List(sel)
} }
// getStatefulSetForPod returns the StatefulSet managing the given pod. // getStatefulSetForPod returns the StatefulSet managing the given pod.
func (ssc *StatefulSetController) getStatefulSetForPod(pod *v1.Pod) *apps.StatefulSet { func (ssc *StatefulSetController) getStatefulSetForPod(pod *v1.Pod) *apps.StatefulSet {
sets, err := ssc.setStore.GetPodStatefulSets(pod) sets, err := ssc.setLister.GetPodStatefulSets(pod)
if err != nil { if err != nil {
glog.V(4).Infof("No StatefulSets found for pod %v, StatefulSet 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
...@@ -225,14 +225,14 @@ func (ssc *StatefulSetController) getStatefulSetForPod(pod *v1.Pod) *apps.Statef ...@@ -225,14 +225,14 @@ func (ssc *StatefulSetController) getStatefulSetForPod(pod *v1.Pod) *apps.Statef
sort.Sort(overlappingStatefulSets(sets)) sort.Sort(overlappingStatefulSets(sets))
// return the first created set for which pod is a member // return the first created set for which pod is a member
for i := range sets { for i := range sets {
if isMemberOf(&sets[i], pod) { if isMemberOf(sets[i], pod) {
return &sets[i] return sets[i]
} }
} }
glog.V(4).Infof("No StatefulSets found for pod %v, StatefulSet 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
} }
return &sets[0] return sets[0]
} }
...@@ -276,8 +276,12 @@ func (ssc *StatefulSetController) sync(key string) error { ...@@ -276,8 +276,12 @@ func (ssc *StatefulSetController) sync(key string) error {
glog.V(4).Infof("Finished syncing statefulset %q (%v)", key, time.Now().Sub(startTime)) glog.V(4).Infof("Finished syncing statefulset %q (%v)", key, time.Now().Sub(startTime))
}() }()
obj, exists, err := ssc.setStore.Store.GetByKey(key) namespace, name, err := cache.SplitMetaNamespaceKey(key)
if !exists { if err != nil {
return err
}
set, err := ssc.setLister.StatefulSets(namespace).Get(name)
if errors.IsNotFound(err) {
glog.Infof("StatefulSet has been deleted %v", key) glog.Infof("StatefulSet has been deleted %v", key)
return nil return nil
} }
...@@ -286,13 +290,12 @@ func (ssc *StatefulSetController) sync(key string) error { ...@@ -286,13 +290,12 @@ func (ssc *StatefulSetController) sync(key string) error {
return err return err
} }
set := *obj.(*apps.StatefulSet) pods, err := ssc.getPodsForStatefulSet(set)
pods, err := ssc.getPodsForStatefulSet(&set)
if err != nil { if err != nil {
return err return err
} }
return ssc.syncStatefulSet(&set, pods) return ssc.syncStatefulSet(set, pods)
} }
// syncStatefulSet syncs a tuple of (statefulset, []*v1.Pod). // syncStatefulSet syncs a tuple of (statefulset, []*v1.Pod).
......
...@@ -17,8 +17,10 @@ limitations under the License. ...@@ -17,8 +17,10 @@ limitations under the License.
package statefulset package statefulset
import ( import (
"fmt"
"sort" "sort"
"k8s.io/client-go/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
...@@ -97,8 +99,16 @@ func (ssc *defaultStatefulSetControl) UpdateStatefulSet(set *apps.StatefulSet, p ...@@ -97,8 +99,16 @@ func (ssc *defaultStatefulSetControl) UpdateStatefulSet(set *apps.StatefulSet, p
sort.Sort(ascendingOrdinal(condemned)) sort.Sort(ascendingOrdinal(condemned))
// if the current number of replicas has changed update the statefulSets replicas // if the current number of replicas has changed update the statefulSets replicas
if err := ssc.podControl.UpdateStatefulSetReplicas(set, int32(ready)); err != nil { if set.Status.Replicas != int32(ready) {
return err obj, err := api.Scheme.Copy(set)
if err != nil {
return fmt.Errorf("unable to copy set: %v", err)
}
set = obj.(*apps.StatefulSet)
if err := ssc.podControl.UpdateStatefulSetReplicas(set, int32(ready)); err != nil {
return err
}
} }
// Examine each replica with respect to its ordinal // Examine each replica with respect to its ordinal
...@@ -123,8 +133,17 @@ func (ssc *defaultStatefulSetControl) UpdateStatefulSet(set *apps.StatefulSet, p ...@@ -123,8 +133,17 @@ func (ssc *defaultStatefulSetControl) UpdateStatefulSet(set *apps.StatefulSet, p
set.Name, replicas[i].Name) set.Name, replicas[i].Name)
return nil return nil
} }
// Enforce the StatefulSet invariants, // Enforce the StatefulSet invariants
if err := ssc.podControl.UpdateStatefulPod(set, replicas[i]); err != nil { if identityMatches(set, replicas[i]) && storageMatches(set, replicas[i]) {
continue
}
// Make a deep copy so we don't mutate the shared cache
copy, err := api.Scheme.DeepCopy(replicas[i])
if err != nil {
return err
}
replica := copy.(*v1.Pod)
if err := ssc.podControl.UpdateStatefulPod(set, replica); err != nil {
return err return err
} }
} }
......
...@@ -24,13 +24,16 @@ import ( ...@@ -24,13 +24,16 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
) )
func alwaysReady() bool { return true }
func TestStatefulSetControllerCreates(t *testing.T) { func TestStatefulSetControllerCreates(t *testing.T) {
ssc, spc := newFakeStatefulSetController() ssc, spc := newFakeStatefulSetController()
set := newStatefulSet(3) set := newStatefulSet(3)
...@@ -343,15 +346,18 @@ func TestStatefulSetControllerGetStatefulSetForPodOverlapping(t *testing.T) { ...@@ -343,15 +346,18 @@ func TestStatefulSetControllerGetStatefulSetForPodOverlapping(t *testing.T) {
} }
func newFakeStatefulSetController() (*StatefulSetController, *fakeStatefulPodControl) { func newFakeStatefulSetController() (*StatefulSetController, *fakeStatefulPodControl) {
fpc := newFakeStatefulPodControl() client := fake.NewSimpleClientset()
ssc := &StatefulSetController{ informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
kubeClient: nil, fpc := newFakeStatefulPodControl(informerFactory.Core().V1().Pods(), informerFactory.Apps().V1beta1().StatefulSets())
podStoreSynced: func() bool { return true }, ssc := NewStatefulSetController(
setStore: fpc.setsLister, informerFactory.Core().V1().Pods(),
podStore: fpc.podsLister, informerFactory.Apps().V1beta1().StatefulSets(),
control: NewDefaultStatefulSetControl(fpc), client,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "statefulset"), )
} ssc.podListerSynced = alwaysReady
ssc.setListerSynced = alwaysReady
ssc.control = NewDefaultStatefulSetControl(fpc)
return ssc, fpc return ssc, fpc
} }
......
...@@ -38,7 +38,7 @@ var updateConflictError = fmt.Errorf("aborting update after %d attempts", maxUpd ...@@ -38,7 +38,7 @@ var updateConflictError = fmt.Errorf("aborting update after %d attempts", maxUpd
// overlappingStatefulSets sorts a list of StatefulSets 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 StatefulSets that have overlapping selectors. // Generally used to tie break between StatefulSets that have overlapping selectors.
type overlappingStatefulSets []apps.StatefulSet type overlappingStatefulSets []*apps.StatefulSet
func (o overlappingStatefulSets) Len() int { return len(o) } func (o overlappingStatefulSets) Len() int { return len(o) }
......
...@@ -251,10 +251,10 @@ func TestAscendingOrdinal(t *testing.T) { ...@@ -251,10 +251,10 @@ func TestAscendingOrdinal(t *testing.T) {
} }
func TestOverlappingStatefulSets(t *testing.T) { func TestOverlappingStatefulSets(t *testing.T) {
sets := make([]apps.StatefulSet, 10) sets := make([]*apps.StatefulSet, 10)
perm := rand.Perm(10) perm := rand.Perm(10)
for i, v := range perm { for i, v := range perm {
sets[i] = *newStatefulSet(10) sets[i] = newStatefulSet(10)
sets[i].CreationTimestamp = metav1.NewTime(sets[i].CreationTimestamp.Add(time.Duration(v) * time.Second)) sets[i].CreationTimestamp = metav1.NewTime(sets[i].CreationTimestamp.Add(time.Duration(v) * time.Second))
} }
sort.Sort(overlappingStatefulSets(sets)) sort.Sort(overlappingStatefulSets(sets))
...@@ -262,7 +262,7 @@ func TestOverlappingStatefulSets(t *testing.T) { ...@@ -262,7 +262,7 @@ func TestOverlappingStatefulSets(t *testing.T) {
t.Error("ascendingOrdinal fails to sort Pods") t.Error("ascendingOrdinal fails to sort Pods")
} }
for i, v := range perm { for i, v := range perm {
sets[i] = *newStatefulSet(10) sets[i] = newStatefulSet(10)
sets[i].Name = strconv.FormatInt(int64(v), 10) sets[i].Name = strconv.FormatInt(int64(v), 10)
} }
sort.Sort(overlappingStatefulSets(sets)) sort.Sort(overlappingStatefulSets(sets))
......
...@@ -264,20 +264,19 @@ var _ = framework.KubeDescribe("[Feature:Example]", func() { ...@@ -264,20 +264,19 @@ var _ = framework.KubeDescribe("[Feature:Example]", func() {
framework.KubeDescribe("CassandraStatefulSet", func() { framework.KubeDescribe("CassandraStatefulSet", func() {
It("should create statefulset", 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("examples/storage/cassandra", file)
} }
serviceYaml := mkpath("cassandra-service.yaml") serviceYaml := mkpath("cassandra-service.yaml")
nsFlag := fmt.Sprintf("--namespace=%v", ns) nsFlag := fmt.Sprintf("--namespace=%v", ns)
// have to change dns prefix because of the dynamic namespace // have to change dns prefix because of the dynamic namespace
input, err := ioutil.ReadFile(mkpath("cassandra-statefulset.yaml")) input := generated.ReadOrDie(mkpath("cassandra-statefulset.yaml"))
Expect(err).NotTo(HaveOccurred())
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)
statefulsetYaml := "/tmp/cassandra-statefulset.yaml" statefulsetYaml := "/tmp/cassandra-statefulset.yaml"
err = ioutil.WriteFile(statefulsetYaml, []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")
......
...@@ -71,6 +71,7 @@ go_library( ...@@ -71,6 +71,7 @@ go_library(
"//pkg/util/version:go_default_library", "//pkg/util/version:go_default_library",
"//plugin/pkg/scheduler/algorithm/predicates:go_default_library", "//plugin/pkg/scheduler/algorithm/predicates:go_default_library",
"//plugin/pkg/scheduler/schedulercache:go_default_library", "//plugin/pkg/scheduler/schedulercache:go_default_library",
"//test/e2e/generated:go_default_library",
"//test/e2e/perftype:go_default_library", "//test/e2e/perftype:go_default_library",
"//test/utils:go_default_library", "//test/utils:go_default_library",
"//vendor:github.com/golang/glog", "//vendor:github.com/golang/glog",
......
...@@ -18,7 +18,6 @@ package framework ...@@ -18,7 +18,6 @@ package framework
import ( import (
"fmt" "fmt"
"io/ioutil"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
...@@ -38,6 +37,7 @@ import ( ...@@ -38,6 +37,7 @@ import (
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/test/e2e/generated"
) )
const ( const (
...@@ -70,8 +70,7 @@ func CreateStatefulSetService(name string, labels map[string]string) *v1.Service ...@@ -70,8 +70,7 @@ func CreateStatefulSetService(name string, labels map[string]string) *v1.Service
func StatefulSetFromManifest(fileName, ns string) *apps.StatefulSet { func StatefulSetFromManifest(fileName, ns string) *apps.StatefulSet {
var ss apps.StatefulSet var ss apps.StatefulSet
Logf("Parsing statefulset from %v", fileName) Logf("Parsing statefulset from %v", fileName)
data, err := ioutil.ReadFile(fileName) data := generated.ReadOrDie(fileName)
Expect(err).NotTo(HaveOccurred())
json, err := utilyaml.ToJSON(data) json, err := utilyaml.ToJSON(data)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
...@@ -99,7 +98,7 @@ func NewStatefulSetTester(c clientset.Interface) *StatefulSetTester { ...@@ -99,7 +98,7 @@ func NewStatefulSetTester(c clientset.Interface) *StatefulSetTester {
// CreateStatefulSet creates a StatefulSet from the manifest at manifestPath in the Namespace ns using kubectl create. // CreateStatefulSet creates a StatefulSet from the manifest at manifestPath in the Namespace ns using kubectl create.
func (s *StatefulSetTester) CreateStatefulSet(manifestPath, ns string) *apps.StatefulSet { func (s *StatefulSetTester) CreateStatefulSet(manifestPath, ns string) *apps.StatefulSet {
mkpath := func(file string) string { mkpath := func(file string) string {
return filepath.Join(TestContext.RepoRoot, manifestPath, file) return filepath.Join(manifestPath, file)
} }
ss := StatefulSetFromManifest(mkpath("statefulset.yaml"), ns) ss := StatefulSetFromManifest(mkpath("statefulset.yaml"), ns)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment