52baf68d50
Signed-off-by: Michał Żyłowski <michal.zylowski@intel.com>
327 lines
8.5 KiB
Go
327 lines
8.5 KiB
Go
/*
|
|
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"
|
|
"time"
|
|
|
|
inf "gopkg.in/inf.v0"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/kubernetes/pkg/api/resource"
|
|
"k8s.io/kubernetes/pkg/api/v1"
|
|
apipod "k8s.io/kubernetes/pkg/api/v1/pod"
|
|
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
|
|
"k8s.io/kubernetes/pkg/client/record"
|
|
)
|
|
|
|
func dec(i int64, exponent int) *inf.Dec {
|
|
return inf.NewDec(i, inf.Scale(-exponent))
|
|
}
|
|
|
|
func newPVC(name string) v1.PersistentVolumeClaim {
|
|
return v1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Spec: v1.PersistentVolumeClaimSpec{
|
|
Resources: v1.ResourceRequirements{
|
|
Requests: v1.ResourceList{
|
|
v1.ResourceStorage: *resource.NewQuantity(1, resource.BinarySI),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func newStatefulSetWithVolumes(replicas int, name string, petMounts []v1.VolumeMount, podMounts []v1.VolumeMount) *apps.StatefulSet {
|
|
mounts := append(petMounts, podMounts...)
|
|
claims := []v1.PersistentVolumeClaim{}
|
|
for _, m := range petMounts {
|
|
claims = append(claims, newPVC(m.Name))
|
|
}
|
|
|
|
vols := []v1.Volume{}
|
|
for _, m := range podMounts {
|
|
vols = append(vols, v1.Volume{
|
|
Name: m.Name,
|
|
VolumeSource: v1.VolumeSource{
|
|
HostPath: &v1.HostPathVolumeSource{
|
|
Path: fmt.Sprintf("/tmp/%v", m.Name),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
return &apps.StatefulSet{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "StatefulSet",
|
|
APIVersion: "apps/v1beta1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: metav1.NamespaceDefault,
|
|
UID: types.UID("test"),
|
|
},
|
|
Spec: apps.StatefulSetSpec{
|
|
Selector: &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{"foo": "bar"},
|
|
},
|
|
Replicas: func() *int32 { i := int32(replicas); return &i }(),
|
|
Template: v1.PodTemplateSpec{
|
|
Spec: v1.PodSpec{
|
|
Containers: []v1.Container{
|
|
{
|
|
Name: "nginx",
|
|
Image: "nginx",
|
|
VolumeMounts: mounts,
|
|
},
|
|
},
|
|
Volumes: vols,
|
|
},
|
|
},
|
|
VolumeClaimTemplates: claims,
|
|
ServiceName: "governingsvc",
|
|
},
|
|
}
|
|
}
|
|
|
|
func runningPod(ns, name string) *v1.Pod {
|
|
p := &v1.Pod{Status: v1.PodStatus{Phase: v1.PodRunning}}
|
|
p.Namespace = ns
|
|
p.Name = name
|
|
return p
|
|
}
|
|
|
|
func newPodList(ps *apps.StatefulSet, num int) []*v1.Pod {
|
|
// knownPods are pods in the system
|
|
knownPods := []*v1.Pod{}
|
|
for i := 0; i < num; i++ {
|
|
k, _ := newPCB(fmt.Sprintf("%v", i), ps)
|
|
knownPods = append(knownPods, k.pod)
|
|
}
|
|
return knownPods
|
|
}
|
|
|
|
func newStatefulSet(replicas int) *apps.StatefulSet {
|
|
petMounts := []v1.VolumeMount{
|
|
{Name: "datadir", MountPath: "/tmp/zookeeper"},
|
|
}
|
|
podMounts := []v1.VolumeMount{
|
|
{Name: "home", MountPath: "/home"},
|
|
}
|
|
return newStatefulSetWithVolumes(replicas, "foo", petMounts, podMounts)
|
|
}
|
|
|
|
func checkPodForMount(pod *v1.Pod, mountName string) error {
|
|
for _, c := range pod.Spec.Containers {
|
|
for _, v := range c.VolumeMounts {
|
|
if v.Name == mountName {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return fmt.Errorf("Found volume but no associated mount %v in pod %v", mountName, pod.Name)
|
|
}
|
|
|
|
func newFakePetClient() *fakePetClient {
|
|
return &fakePetClient{
|
|
pets: []*pcb{},
|
|
claims: []v1.PersistentVolumeClaim{},
|
|
recorder: &record.FakeRecorder{},
|
|
petHealthChecker: &defaultPetHealthChecker{},
|
|
}
|
|
}
|
|
|
|
type fakePetClient struct {
|
|
pets []*pcb
|
|
claims []v1.PersistentVolumeClaim
|
|
petsCreated int
|
|
petsDeleted int
|
|
claimsCreated int
|
|
claimsDeleted int
|
|
recorder record.EventRecorder
|
|
petHealthChecker
|
|
}
|
|
|
|
// Delete fakes pet client deletion.
|
|
func (f *fakePetClient) Delete(p *pcb) error {
|
|
pets := []*pcb{}
|
|
found := false
|
|
for i, pet := range f.pets {
|
|
if p.pod.Name == pet.pod.Name {
|
|
found = true
|
|
f.recorder.Eventf(pet.parent, v1.EventTypeNormal, "SuccessfulDelete", "pod: %v", pet.pod.Name)
|
|
continue
|
|
}
|
|
pets = append(pets, f.pets[i])
|
|
}
|
|
if !found {
|
|
// TODO: Return proper not found error
|
|
return fmt.Errorf("Delete failed: pod %v doesn't exist", p.pod.Name)
|
|
}
|
|
f.pets = pets
|
|
f.petsDeleted++
|
|
return nil
|
|
}
|
|
|
|
// Get fakes getting pets.
|
|
func (f *fakePetClient) Get(p *pcb) (*pcb, bool, error) {
|
|
for i, pet := range f.pets {
|
|
if p.pod.Name == pet.pod.Name {
|
|
return f.pets[i], true, nil
|
|
}
|
|
}
|
|
return nil, false, nil
|
|
}
|
|
|
|
// Create fakes pet creation.
|
|
func (f *fakePetClient) Create(p *pcb) error {
|
|
for _, pet := range f.pets {
|
|
if p.pod.Name == pet.pod.Name {
|
|
return fmt.Errorf("Create failed: pod %v already exists", p.pod.Name)
|
|
}
|
|
}
|
|
f.recorder.Eventf(p.parent, v1.EventTypeNormal, "SuccessfulCreate", "pod: %v", p.pod.Name)
|
|
f.pets = append(f.pets, p)
|
|
f.petsCreated++
|
|
return nil
|
|
}
|
|
|
|
// Update fakes pet updates.
|
|
func (f *fakePetClient) Update(expected, wanted *pcb) error {
|
|
found := false
|
|
pets := []*pcb{}
|
|
for i, pet := range f.pets {
|
|
if wanted.pod.Name == pet.pod.Name {
|
|
f.pets[i].pod.Annotations[apipod.PodHostnameAnnotation] = wanted.pod.Annotations[apipod.PodHostnameAnnotation]
|
|
f.pets[i].pod.Annotations[apipod.PodSubdomainAnnotation] = wanted.pod.Annotations[apipod.PodSubdomainAnnotation]
|
|
f.pets[i].pod.Spec = wanted.pod.Spec
|
|
found = true
|
|
}
|
|
pets = append(pets, f.pets[i])
|
|
}
|
|
f.pets = pets
|
|
if !found {
|
|
return fmt.Errorf("Cannot update pod %v not found", wanted.pod.Name)
|
|
}
|
|
// TODO: Delete pvcs/volumes that are in wanted but not in expected.
|
|
return nil
|
|
}
|
|
|
|
func (f *fakePetClient) getPodList() []*v1.Pod {
|
|
p := []*v1.Pod{}
|
|
for i, pet := range f.pets {
|
|
if pet.pod == nil {
|
|
continue
|
|
}
|
|
p = append(p, f.pets[i].pod)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (f *fakePetClient) deletePetAtIndex(index int) {
|
|
p := []*pcb{}
|
|
for i := range f.pets {
|
|
if i != index {
|
|
p = append(p, f.pets[i])
|
|
}
|
|
}
|
|
f.pets = p
|
|
}
|
|
|
|
func (f *fakePetClient) setHealthy(index int) error {
|
|
if 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 = v1.PodRunning
|
|
f.pets[index].pod.Annotations[StatefulSetInitAnnotation] = "true"
|
|
f.pets[index].pod.Status.Conditions = []v1.PodCondition{
|
|
{Type: v1.PodReady, Status: v1.ConditionTrue},
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// isHealthy is a convenience wrapper around the default health checker.
|
|
// The first invocation returns not-healthy, but marks the pet healthy so
|
|
// subsequent invocations see it as healthy.
|
|
func (f *fakePetClient) isHealthy(pod *v1.Pod) bool {
|
|
if f.petHealthChecker.isHealthy(pod) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (f *fakePetClient) setDeletionTimestamp(index int) error {
|
|
if len(f.pets) <= index {
|
|
return fmt.Errorf("Index out of range, len %v index %v", len(f.pets), index)
|
|
}
|
|
f.pets[index].pod.DeletionTimestamp = &metav1.Time{Time: time.Now()}
|
|
return nil
|
|
}
|
|
|
|
// SyncPVCs fakes pvc syncing.
|
|
func (f *fakePetClient) SyncPVCs(pet *pcb) error {
|
|
v := pet.pvcs
|
|
updateClaims := map[string]v1.PersistentVolumeClaim{}
|
|
for i, update := range v {
|
|
updateClaims[update.Name] = v[i]
|
|
}
|
|
claimList := []v1.PersistentVolumeClaim{}
|
|
for i, existing := range f.claims {
|
|
if update, ok := updateClaims[existing.Name]; ok {
|
|
claimList = append(claimList, update)
|
|
delete(updateClaims, existing.Name)
|
|
} else {
|
|
claimList = append(claimList, f.claims[i])
|
|
}
|
|
}
|
|
for _, remaining := range updateClaims {
|
|
claimList = append(claimList, remaining)
|
|
f.claimsCreated++
|
|
f.recorder.Eventf(pet.parent, v1.EventTypeNormal, "SuccessfulCreate", "pvc: %v", remaining.Name)
|
|
}
|
|
f.claims = claimList
|
|
return nil
|
|
}
|
|
|
|
// DeletePVCs fakes pvc deletion.
|
|
func (f *fakePetClient) DeletePVCs(pet *pcb) error {
|
|
claimsToDelete := pet.pvcs
|
|
deleteClaimNames := sets.NewString()
|
|
for _, c := range claimsToDelete {
|
|
deleteClaimNames.Insert(c.Name)
|
|
}
|
|
pvcs := []v1.PersistentVolumeClaim{}
|
|
for i, existing := range f.claims {
|
|
if deleteClaimNames.Has(existing.Name) {
|
|
deleteClaimNames.Delete(existing.Name)
|
|
f.claimsDeleted++
|
|
f.recorder.Eventf(pet.parent, v1.EventTypeNormal, "SuccessfulDelete", "pvc: %v", existing.Name)
|
|
continue
|
|
}
|
|
pvcs = append(pvcs, f.claims[i])
|
|
}
|
|
f.claims = pvcs
|
|
if deleteClaimNames.Len() != 0 {
|
|
return fmt.Errorf("Claims %+v don't exist. Failed deletion.", deleteClaimNames)
|
|
}
|
|
return nil
|
|
}
|