2017-02-08 19:30:14 +00:00
|
|
|
/*
|
|
|
|
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 statefulset
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2017-06-22 18:24:23 +00:00
|
|
|
apps "k8s.io/api/apps/v1beta1"
|
|
|
|
"k8s.io/api/core/v1"
|
2017-02-08 19:30:14 +00:00
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
errorutils "k8s.io/apimachinery/pkg/util/errors"
|
2017-02-14 19:03:00 +00:00
|
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
2017-06-23 20:56:37 +00:00
|
|
|
clientset "k8s.io/client-go/kubernetes"
|
2017-04-27 00:13:48 +00:00
|
|
|
"k8s.io/client-go/kubernetes/scheme"
|
2017-06-23 20:56:37 +00:00
|
|
|
appslisters "k8s.io/client-go/listers/apps/v1beta1"
|
|
|
|
corelisters "k8s.io/client-go/listers/core/v1"
|
2017-02-08 19:30:14 +00:00
|
|
|
"k8s.io/client-go/tools/record"
|
2017-08-10 07:03:41 +00:00
|
|
|
"k8s.io/client-go/util/retry"
|
2017-02-08 19:30:14 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// StatefulPodControlInterface defines the interface that StatefulSetController uses to create, update, and delete Pods,
|
|
|
|
// and to update the Status of a StatefulSet. It follows the design paradigms used for PodControl, but its
|
|
|
|
// implementation provides for PVC creation, ordered Pod creation, ordered Pod termination, and Pod identity enforcement.
|
|
|
|
// Like controller.PodControlInterface, it is implemented as an interface to provide for testing fakes.
|
|
|
|
type StatefulPodControlInterface interface {
|
|
|
|
// CreateStatefulPod create a Pod in a StatefulSet. Any PVCs necessary for the Pod are created prior to creating
|
|
|
|
// the Pod. If the returned error is nil the Pod and its PVCs have been created.
|
|
|
|
CreateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error
|
|
|
|
// UpdateStatefulPod Updates a Pod in a StatefulSet. If the Pod already has the correct identity and stable
|
|
|
|
// storage this method is a no-op. If the Pod must be mutated to conform to the Set, it is mutated and updated.
|
|
|
|
// pod is an in-out parameter, and any updates made to the pod are reflected as mutations to this parameter. If
|
|
|
|
// the create is successful, the returned error is nil.
|
|
|
|
UpdateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error
|
|
|
|
// DeleteStatefulPod deletes a Pod in a StatefulSet. The pods PVCs are not deleted. If the delete is successful,
|
|
|
|
// the returned error is nil.
|
|
|
|
DeleteStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error
|
|
|
|
}
|
|
|
|
|
2017-02-24 14:31:40 +00:00
|
|
|
func NewRealStatefulPodControl(
|
|
|
|
client clientset.Interface,
|
|
|
|
setLister appslisters.StatefulSetLister,
|
|
|
|
podLister corelisters.PodLister,
|
|
|
|
pvcLister corelisters.PersistentVolumeClaimLister,
|
|
|
|
recorder record.EventRecorder,
|
|
|
|
) StatefulPodControlInterface {
|
|
|
|
return &realStatefulPodControl{client, setLister, podLister, pvcLister, recorder}
|
2017-02-08 19:30:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
type realStatefulPodControl struct {
|
2017-02-14 19:03:00 +00:00
|
|
|
client clientset.Interface
|
|
|
|
setLister appslisters.StatefulSetLister
|
|
|
|
podLister corelisters.PodLister
|
2017-02-24 14:31:40 +00:00
|
|
|
pvcLister corelisters.PersistentVolumeClaimLister
|
2017-02-14 19:03:00 +00:00
|
|
|
recorder record.EventRecorder
|
2017-02-08 19:30:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (spc *realStatefulPodControl) CreateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error {
|
|
|
|
// Create the Pod's PVCs prior to creating the Pod
|
|
|
|
if err := spc.createPersistentVolumeClaims(set, pod); err != nil {
|
|
|
|
spc.recordPodEvent("create", set, pod, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// If we created the PVCs attempt to create the Pod
|
|
|
|
_, err := spc.client.Core().Pods(set.Namespace).Create(pod)
|
|
|
|
// sink already exists errors
|
|
|
|
if apierrors.IsAlreadyExists(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
spc.recordPodEvent("create", set, pod, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (spc *realStatefulPodControl) UpdateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error {
|
2017-02-14 19:03:00 +00:00
|
|
|
attemptedUpdate := false
|
|
|
|
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
2017-02-08 19:30:14 +00:00
|
|
|
// assume the Pod is consistent
|
|
|
|
consistent := true
|
2017-02-14 19:03:00 +00:00
|
|
|
// if the Pod does not conform to its identity, update the identity and dirty the Pod
|
|
|
|
if !identityMatches(set, pod) {
|
|
|
|
updateIdentity(set, pod)
|
2017-02-08 19:30:14 +00:00
|
|
|
consistent = false
|
|
|
|
}
|
|
|
|
// 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
|
2017-02-14 19:03:00 +00:00
|
|
|
if !storageMatches(set, pod) {
|
|
|
|
updateStorage(set, pod)
|
2017-02-08 19:30:14 +00:00
|
|
|
consistent = false
|
2017-02-14 19:03:00 +00:00
|
|
|
if err := spc.createPersistentVolumeClaims(set, pod); err != nil {
|
2017-02-08 19:30:14 +00:00
|
|
|
spc.recordPodEvent("update", set, pod, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-04-28 10:16:19 +00:00
|
|
|
// if the Pod is not dirty, do nothing
|
2017-02-08 19:30:14 +00:00
|
|
|
if consistent {
|
|
|
|
return nil
|
|
|
|
}
|
2017-02-14 19:03:00 +00:00
|
|
|
|
|
|
|
attemptedUpdate = true
|
2017-02-08 19:30:14 +00:00
|
|
|
// commit the update, retrying on conflicts
|
2017-04-28 10:16:19 +00:00
|
|
|
_, updateErr := spc.client.Core().Pods(set.Namespace).Update(pod)
|
|
|
|
if updateErr == nil {
|
2017-02-14 19:03:00 +00:00
|
|
|
return nil
|
2017-02-08 19:30:14 +00:00
|
|
|
}
|
2017-02-14 19:03:00 +00:00
|
|
|
|
|
|
|
if updated, err := spc.podLister.Pods(set.Namespace).Get(pod.Name); err == nil {
|
|
|
|
// make a copy so we don't mutate the shared cache
|
2017-04-27 00:13:48 +00:00
|
|
|
if copy, err := scheme.Scheme.DeepCopy(updated); err == nil {
|
2017-02-14 19:03:00 +00:00
|
|
|
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))
|
2017-02-08 19:30:14 +00:00
|
|
|
}
|
2017-02-14 19:03:00 +00:00
|
|
|
|
|
|
|
return updateErr
|
|
|
|
})
|
|
|
|
if attemptedUpdate {
|
|
|
|
spc.recordPodEvent("update", set, pod, err)
|
2017-02-08 19:30:14 +00:00
|
|
|
}
|
2017-02-14 19:03:00 +00:00
|
|
|
return err
|
2017-02-08 19:30:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (spc *realStatefulPodControl) DeleteStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error {
|
|
|
|
err := spc.client.Core().Pods(set.Namespace).Delete(pod.Name, nil)
|
|
|
|
spc.recordPodEvent("delete", set, pod, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// recordPodEvent records an event for verb applied to a Pod in a StatefulSet. If err is nil the generated event will
|
|
|
|
// have a reason of v1.EventTypeNormal. If err is not nil the generated event will have a reason of v1.EventTypeWarning.
|
|
|
|
func (spc *realStatefulPodControl) recordPodEvent(verb string, set *apps.StatefulSet, pod *v1.Pod, err error) {
|
|
|
|
if err == nil {
|
|
|
|
reason := fmt.Sprintf("Successful%s", strings.Title(verb))
|
|
|
|
message := fmt.Sprintf("%s Pod %s in StatefulSet %s successful",
|
|
|
|
strings.ToLower(verb), pod.Name, set.Name)
|
|
|
|
spc.recorder.Event(set, v1.EventTypeNormal, reason, message)
|
|
|
|
} else {
|
|
|
|
reason := fmt.Sprintf("Failed%s", strings.Title(verb))
|
|
|
|
message := fmt.Sprintf("%s Pod %s in StatefulSet %s failed error: %s",
|
|
|
|
strings.ToLower(verb), pod.Name, set.Name, err)
|
|
|
|
spc.recorder.Event(set, v1.EventTypeWarning, reason, message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// recordClaimEvent records an event for verb applied to the PersistentVolumeClaim of a Pod in a StatefulSet. If err is
|
|
|
|
// nil the generated event will have a reason of v1.EventTypeNormal. If err is not nil the generated event will have a
|
|
|
|
// reason of v1.EventTypeWarning.
|
|
|
|
func (spc *realStatefulPodControl) recordClaimEvent(verb string, set *apps.StatefulSet, pod *v1.Pod, claim *v1.PersistentVolumeClaim, err error) {
|
|
|
|
if err == nil {
|
|
|
|
reason := fmt.Sprintf("Successful%s", strings.Title(verb))
|
|
|
|
message := fmt.Sprintf("%s Claim %s Pod %s in StatefulSet %s success",
|
|
|
|
strings.ToLower(verb), claim.Name, pod.Name, set.Name)
|
|
|
|
spc.recorder.Event(set, v1.EventTypeNormal, reason, message)
|
|
|
|
} else {
|
|
|
|
reason := fmt.Sprintf("Failed%s", strings.Title(verb))
|
|
|
|
message := fmt.Sprintf("%s Claim %s for Pod %s in StatefulSet %s failed error: %s",
|
|
|
|
strings.ToLower(verb), claim.Name, pod.Name, set.Name, err)
|
|
|
|
spc.recorder.Event(set, v1.EventTypeWarning, reason, message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// createPersistentVolumeClaims creates all of the required PersistentVolumeClaims for pod, which mush be a member of
|
|
|
|
// set. If all of the claims for Pod are successfully created, the returned error is nil. If creation fails, this method
|
|
|
|
// may be called again until no error is returned, indicating the PersistentVolumeClaims for pod are consistent with
|
|
|
|
// set's Spec.
|
|
|
|
func (spc *realStatefulPodControl) createPersistentVolumeClaims(set *apps.StatefulSet, pod *v1.Pod) error {
|
|
|
|
var errs []error
|
|
|
|
for _, claim := range getPersistentVolumeClaims(set, pod) {
|
2017-02-24 14:31:40 +00:00
|
|
|
_, err := spc.pvcLister.PersistentVolumeClaims(claim.Namespace).Get(claim.Name)
|
|
|
|
switch {
|
|
|
|
case apierrors.IsNotFound(err):
|
|
|
|
_, err := spc.client.Core().PersistentVolumeClaims(claim.Namespace).Create(&claim)
|
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("Failed to create PVC %s: %s", claim.Name, err))
|
|
|
|
}
|
|
|
|
if err == nil || !apierrors.IsAlreadyExists(err) {
|
2017-02-08 19:30:14 +00:00
|
|
|
spc.recordClaimEvent("create", set, pod, &claim, err)
|
|
|
|
}
|
2017-02-24 14:31:40 +00:00
|
|
|
case err != nil:
|
|
|
|
errs = append(errs, fmt.Errorf("Failed to retrieve PVC %s: %s", claim.Name, err))
|
|
|
|
spc.recordClaimEvent("create", set, pod, &claim, err)
|
2017-02-08 19:30:14 +00:00
|
|
|
}
|
|
|
|
// TODO: Check resource requirements and accessmodes, update if necessary
|
|
|
|
}
|
|
|
|
return errorutils.NewAggregate(errs)
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ StatefulPodControlInterface = &realStatefulPodControl{}
|