2017-02-01 00:45:59 +00:00
/ *
Copyright 2014 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 e2e
import (
"fmt"
"io/ioutil"
"path/filepath"
"strconv"
"strings"
"time"
inf "gopkg.in/inf.v0"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
klabels "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/v1"
apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
2017-02-03 13:41:32 +00:00
"k8s.io/kubernetes/pkg/controller/statefulset"
2017-02-01 00:45:59 +00:00
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/kubernetes/test/e2e/framework"
)
const (
statefulsetPoll = 10 * time . Second
2017-02-03 13:41:32 +00:00
// Some statefulPods install base packages via wget
2017-02-01 00:45:59 +00:00
statefulsetTimeout = 10 * time . Minute
// Timeout for stateful pods to change state
2017-02-03 13:41:32 +00:00
statefulPodTimeout = 5 * time . Minute
zookeeperManifestPath = "test/e2e/testing-manifests/statefulset/zookeeper"
mysqlGaleraManifestPath = "test/e2e/testing-manifests/statefulset/mysql-galera"
redisManifestPath = "test/e2e/testing-manifests/statefulset/redis"
cockroachDBManifestPath = "test/e2e/testing-manifests/statefulset/cockroachdb"
2017-02-01 00:45:59 +00:00
// We don't restart MySQL cluster regardless of restartCluster, since MySQL doesn't handle restart well
restartCluster = true
// Timeout for reads from databases running on stateful pods.
readTimeout = 60 * time . Second
)
// GCE Quota requirements: 3 pds, one per stateful pod manifest declared above.
// GCE Api requirements: nodes and master need storage r/w permissions.
var _ = framework . KubeDescribe ( "StatefulSet" , func ( ) {
f := framework . NewDefaultFramework ( "statefulset" )
var ns string
var c clientset . Interface
BeforeEach ( func ( ) {
c = f . ClientSet
ns = f . Namespace . Name
} )
framework . KubeDescribe ( "Basic StatefulSet functionality" , func ( ) {
2017-02-03 13:41:32 +00:00
ssName := "ss"
2017-02-01 00:45:59 +00:00
labels := map [ string ] string {
"foo" : "bar" ,
"baz" : "blah" ,
}
headlessSvcName := "test"
2017-02-03 13:41:32 +00:00
var statefulPodMounts , podMounts [ ] v1 . VolumeMount
var ss * apps . StatefulSet
2017-02-01 00:45:59 +00:00
BeforeEach ( func ( ) {
2017-02-03 13:41:32 +00:00
statefulPodMounts = [ ] v1 . VolumeMount { { Name : "datadir" , MountPath : "/data/" } }
2017-02-01 00:45:59 +00:00
podMounts = [ ] v1 . VolumeMount { { Name : "home" , MountPath : "/home" } }
2017-02-03 13:41:32 +00:00
ss = newStatefulSet ( ssName , ns , headlessSvcName , 2 , statefulPodMounts , podMounts , labels )
2017-02-01 00:45:59 +00:00
By ( "Creating service " + headlessSvcName + " in namespace " + ns )
headlessService := createServiceSpec ( headlessSvcName , "" , true , labels )
_ , err := c . Core ( ) . Services ( ns ) . Create ( headlessService )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} )
AfterEach ( func ( ) {
if CurrentGinkgoTestDescription ( ) . Failed {
dumpDebugInfo ( c , ns )
}
framework . Logf ( "Deleting all statefulset in ns %v" , ns )
deleteAllStatefulSets ( c , ns )
} )
It ( "should provide basic identity" , func ( ) {
2017-02-03 13:41:32 +00:00
By ( "Creating statefulset " + ssName + " in namespace " + ns )
* ( ss . Spec . Replicas ) = 3
setInitializedAnnotation ( ss , "false" )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
_ , err := c . Apps ( ) . StatefulSets ( ns ) . Create ( ss )
2017-02-01 00:45:59 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
sst := statefulSetTester { c : c }
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
By ( "Saturating stateful set " + ss . Name )
sst . saturate ( ss )
2017-02-01 00:45:59 +00:00
By ( "Verifying statefulset mounted data directory is usable" )
2017-02-03 13:41:32 +00:00
framework . ExpectNoError ( sst . checkMount ( ss , "/data" ) )
2017-02-01 00:45:59 +00:00
By ( "Verifying statefulset provides a stable hostname for each pod" )
2017-02-03 13:41:32 +00:00
framework . ExpectNoError ( sst . checkHostname ( ss ) )
2017-02-01 00:45:59 +00:00
cmd := "echo $(hostname) > /data/hostname; sync;"
By ( "Running " + cmd + " in all stateful pods" )
2017-02-03 13:41:32 +00:00
framework . ExpectNoError ( sst . execInStatefulPods ( ss , cmd ) )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
By ( "Restarting statefulset " + ss . Name )
sst . restart ( ss )
sst . saturate ( ss )
2017-02-01 00:45:59 +00:00
By ( "Verifying statefulset mounted data directory is usable" )
2017-02-03 13:41:32 +00:00
framework . ExpectNoError ( sst . checkMount ( ss , "/data" ) )
2017-02-01 00:45:59 +00:00
cmd = "if [ \"$(cat /data/hostname)\" = \"$(hostname)\" ]; then exit 0; else exit 1; fi"
By ( "Running " + cmd + " in all stateful pods" )
2017-02-03 13:41:32 +00:00
framework . ExpectNoError ( sst . execInStatefulPods ( ss , cmd ) )
2017-02-01 00:45:59 +00:00
} )
It ( "should handle healthy stateful pod restarts during scale" , func ( ) {
2017-02-03 13:41:32 +00:00
By ( "Creating statefulset " + ssName + " in namespace " + ns )
* ( ss . Spec . Replicas ) = 2
setInitializedAnnotation ( ss , "false" )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
_ , err := c . Apps ( ) . StatefulSets ( ns ) . Create ( ss )
2017-02-01 00:45:59 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
sst := statefulSetTester { c : c }
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
sst . waitForRunningAndReady ( 1 , ss )
2017-02-01 00:45:59 +00:00
By ( "Marking stateful pod at index 0 as healthy." )
2017-02-03 13:41:32 +00:00
sst . setHealthy ( ss )
2017-02-01 00:45:59 +00:00
By ( "Waiting for stateful pod at index 1 to enter running." )
2017-02-03 13:41:32 +00:00
sst . waitForRunningAndReady ( 2 , ss )
2017-02-01 00:45:59 +00:00
// Now we have 1 healthy and 1 unhealthy stateful pod. Deleting the healthy stateful pod should *not*
// create a new stateful pod till the remaining stateful pod becomes healthy, which won't happen till
// we set the healthy bit.
By ( "Deleting healthy stateful pod at index 0." )
2017-02-03 13:41:32 +00:00
sst . deleteStatefulPodAtIndex ( 0 , ss )
2017-02-01 00:45:59 +00:00
By ( "Confirming stateful pod at index 0 is not recreated." )
2017-02-03 13:41:32 +00:00
sst . confirmStatefulPodCount ( 1 , ss , 10 * time . Second )
2017-02-01 00:45:59 +00:00
By ( "Deleting unhealthy stateful pod at index 1." )
2017-02-03 13:41:32 +00:00
sst . deleteStatefulPodAtIndex ( 1 , ss )
2017-02-01 00:45:59 +00:00
By ( "Confirming all stateful pods in statefulset are created." )
2017-02-03 13:41:32 +00:00
sst . saturate ( ss )
2017-02-01 00:45:59 +00:00
} )
It ( "should allow template updates" , func ( ) {
2017-02-03 13:41:32 +00:00
By ( "Creating stateful set " + ssName + " in namespace " + ns )
* ( ss . Spec . Replicas ) = 2
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
ss , err := c . Apps ( ) . StatefulSets ( ns ) . Create ( ss )
2017-02-01 00:45:59 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
sst := statefulSetTester { c : c }
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
sst . waitForRunningAndReady ( * ss . Spec . Replicas , ss )
2017-02-01 00:45:59 +00:00
newImage := newNginxImage
2017-02-03 13:41:32 +00:00
oldImage := ss . Spec . Template . Spec . Containers [ 0 ] . Image
2017-02-01 00:45:59 +00:00
By ( fmt . Sprintf ( "Updating stateful set template: update image from %s to %s" , oldImage , newImage ) )
Expect ( oldImage ) . NotTo ( Equal ( newImage ) , "Incorrect test setup: should update to a different image" )
2017-02-03 13:41:32 +00:00
_ , err = framework . UpdateStatefulSetWithRetries ( c , ns , ss . Name , func ( update * apps . StatefulSet ) {
2017-02-01 00:45:59 +00:00
update . Spec . Template . Spec . Containers [ 0 ] . Image = newImage
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
updateIndex := 0
By ( fmt . Sprintf ( "Deleting stateful pod at index %d" , updateIndex ) )
2017-02-03 13:41:32 +00:00
sst . deleteStatefulPodAtIndex ( updateIndex , ss )
2017-02-01 00:45:59 +00:00
By ( "Waiting for all stateful pods to be running again" )
2017-02-03 13:41:32 +00:00
sst . waitForRunningAndReady ( * ss . Spec . Replicas , ss )
2017-02-01 00:45:59 +00:00
By ( fmt . Sprintf ( "Verifying stateful pod at index %d is updated" , updateIndex ) )
verify := func ( pod * v1 . Pod ) {
podImage := pod . Spec . Containers [ 0 ] . Image
Expect ( podImage ) . To ( Equal ( newImage ) , fmt . Sprintf ( "Expected stateful pod image %s updated to %s" , podImage , newImage ) )
}
2017-02-03 13:41:32 +00:00
sst . verifyPodAtIndex ( updateIndex , ss , verify )
2017-02-01 00:45:59 +00:00
} )
It ( "Scaling down before scale up is finished should wait until current pod will be running and ready before it will be removed" , func ( ) {
2017-02-03 13:41:32 +00:00
By ( "Creating stateful set " + ssName + " in namespace " + ns + ", and pausing scale operations after each pod" )
2017-02-01 00:45:59 +00:00
testProbe := & v1 . Probe { Handler : v1 . Handler { HTTPGet : & v1 . HTTPGetAction {
Path : "/index.html" ,
Port : intstr . IntOrString { IntVal : 80 } } } }
2017-02-03 13:41:32 +00:00
ss := newStatefulSet ( ssName , ns , headlessSvcName , 1 , nil , nil , labels )
ss . Spec . Template . Spec . Containers [ 0 ] . ReadinessProbe = testProbe
setInitializedAnnotation ( ss , "false" )
ss , err := c . Apps ( ) . StatefulSets ( ns ) . Create ( ss )
2017-02-01 00:45:59 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
sst := & statefulSetTester { c : c }
sst . waitForRunningAndReady ( 1 , ss )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
By ( "Scaling up stateful set " + ssName + " to 3 replicas and pausing after 2nd pod" )
sst . setHealthy ( ss )
sst . updateReplicas ( ss , 3 )
sst . waitForRunningAndReady ( 2 , ss )
2017-02-01 00:45:59 +00:00
By ( "Before scale up finished setting 2nd pod to be not ready by breaking readiness probe" )
2017-02-03 13:41:32 +00:00
sst . breakProbe ( ss , testProbe )
sst . waitForRunningAndNotReady ( 2 , ss )
2017-02-01 00:45:59 +00:00
By ( "Continue scale operation after the 2nd pod, and scaling down to 1 replica" )
2017-02-03 13:41:32 +00:00
sst . setHealthy ( ss )
sst . updateReplicas ( ss , 1 )
2017-02-01 00:45:59 +00:00
By ( "Verifying that the 2nd pod wont be removed if it is not running and ready" )
2017-02-03 13:41:32 +00:00
sst . confirmStatefulPodCount ( 2 , ss , 10 * time . Second )
expectedPodName := ss . Name + "-1"
2017-02-01 00:45:59 +00:00
expectedPod , err := f . ClientSet . Core ( ) . Pods ( ns ) . Get ( expectedPodName , metav1 . GetOptions { } )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
watcher , err := f . ClientSet . Core ( ) . Pods ( ns ) . Watch ( metav1 . SingleObject (
metav1 . ObjectMeta {
2017-02-01 00:45:59 +00:00
Name : expectedPod . Name ,
ResourceVersion : expectedPod . ResourceVersion ,
} ,
) )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
By ( "Verifying the 2nd pod is removed only when it becomes running and ready" )
2017-02-03 13:41:32 +00:00
sst . restoreProbe ( ss , testProbe )
2017-02-01 00:45:59 +00:00
_ , err = watch . Until ( statefulsetTimeout , watcher , func ( event watch . Event ) ( bool , error ) {
pod := event . Object . ( * v1 . Pod )
if event . Type == watch . Deleted && pod . Name == expectedPodName {
return false , fmt . Errorf ( "Pod %v was deleted before enter running" , pod . Name )
}
framework . Logf ( "Observed event %v for pod %v. Phase %v, Pod is ready %v" ,
event . Type , pod . Name , pod . Status . Phase , v1 . IsPodReady ( pod ) )
if pod . Name != expectedPodName {
return false , nil
}
if pod . Status . Phase == v1 . PodRunning && v1 . IsPodReady ( pod ) {
return true , nil
}
return false , nil
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} )
It ( "Scaling should happen in predictable order and halt if any stateful pod is unhealthy" , func ( ) {
psLabels := klabels . Set ( labels )
By ( "Initializing watcher for selector " + psLabels . String ( ) )
2017-02-03 13:41:32 +00:00
watcher , err := f . ClientSet . Core ( ) . Pods ( ns ) . Watch ( metav1 . ListOptions {
2017-02-01 00:45:59 +00:00
LabelSelector : psLabels . AsSelector ( ) . String ( ) ,
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
By ( "Creating stateful set " + ssName + " in namespace " + ns )
2017-02-01 00:45:59 +00:00
testProbe := & v1 . Probe { Handler : v1 . Handler { HTTPGet : & v1 . HTTPGetAction {
Path : "/index.html" ,
Port : intstr . IntOrString { IntVal : 80 } } } }
2017-02-03 13:41:32 +00:00
ss := newStatefulSet ( ssName , ns , headlessSvcName , 1 , nil , nil , psLabels )
ss . Spec . Template . Spec . Containers [ 0 ] . ReadinessProbe = testProbe
ss , err = c . Apps ( ) . StatefulSets ( ns ) . Create ( ss )
2017-02-01 00:45:59 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
By ( "Waiting until all stateful set " + ssName + " replicas will be running in namespace " + ns )
sst := & statefulSetTester { c : c }
sst . waitForRunningAndReady ( * ss . Spec . Replicas , ss )
2017-02-01 00:45:59 +00:00
By ( "Confirming that stateful set scale up will halt with unhealthy stateful pod" )
2017-02-03 13:41:32 +00:00
sst . breakProbe ( ss , testProbe )
sst . waitForRunningAndNotReady ( * ss . Spec . Replicas , ss )
sst . updateReplicas ( ss , 3 )
sst . confirmStatefulPodCount ( 1 , ss , 10 * time . Second )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
By ( "Scaling up stateful set " + ssName + " to 3 replicas and waiting until all of them will be running in namespace " + ns )
sst . restoreProbe ( ss , testProbe )
sst . waitForRunningAndReady ( 3 , ss )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
By ( "Verifying that stateful set " + ssName + " was scaled up in order" )
expectedOrder := [ ] string { ssName + "-0" , ssName + "-1" , ssName + "-2" }
2017-02-01 00:45:59 +00:00
_ , err = watch . Until ( statefulsetTimeout , watcher , func ( event watch . Event ) ( bool , error ) {
if event . Type != watch . Added {
return false , nil
}
pod := event . Object . ( * v1 . Pod )
if pod . Name == expectedOrder [ 0 ] {
expectedOrder = expectedOrder [ 1 : ]
}
return len ( expectedOrder ) == 0 , nil
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
By ( "Scale down will halt with unhealthy stateful pod" )
2017-02-03 13:41:32 +00:00
watcher , err = f . ClientSet . Core ( ) . Pods ( ns ) . Watch ( metav1 . ListOptions {
2017-02-01 00:45:59 +00:00
LabelSelector : psLabels . AsSelector ( ) . String ( ) ,
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
sst . breakProbe ( ss , testProbe )
sst . waitForRunningAndNotReady ( 3 , ss )
sst . updateReplicas ( ss , 0 )
sst . confirmStatefulPodCount ( 3 , ss , 10 * time . Second )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
By ( "Scaling down stateful set " + ssName + " to 0 replicas and waiting until none of pods will run in namespace" + ns )
sst . restoreProbe ( ss , testProbe )
sst . scale ( ss , 0 )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
By ( "Verifying that stateful set " + ssName + " was scaled down in reverse order" )
expectedOrder = [ ] string { ssName + "-2" , ssName + "-1" , ssName + "-0" }
2017-02-01 00:45:59 +00:00
_ , err = watch . Until ( statefulsetTimeout , watcher , func ( event watch . Event ) ( bool , error ) {
if event . Type != watch . Deleted {
return false , nil
}
pod := event . Object . ( * v1 . Pod )
if pod . Name == expectedOrder [ 0 ] {
expectedOrder = expectedOrder [ 1 : ]
}
return len ( expectedOrder ) == 0 , nil
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} )
It ( "Should recreate evicted statefulset" , func ( ) {
podName := "test-pod"
2017-02-03 13:41:32 +00:00
statefulPodName := ssName + "-0"
2017-02-01 00:45:59 +00:00
By ( "Looking for a node to schedule stateful set and pod" )
nodes := framework . GetReadySchedulableNodesOrDie ( f . ClientSet )
node := nodes . Items [ 0 ]
By ( "Creating pod with conflicting port in namespace " + f . Namespace . Name )
conflictingPort := v1 . ContainerPort { HostPort : 21017 , ContainerPort : 21017 , Name : "conflict" }
pod := & v1 . Pod {
2017-02-03 13:41:32 +00:00
ObjectMeta : metav1 . ObjectMeta {
2017-02-01 00:45:59 +00:00
Name : podName ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "nginx" ,
Image : "gcr.io/google_containers/nginx-slim:0.7" ,
Ports : [ ] v1 . ContainerPort { conflictingPort } ,
} ,
} ,
NodeName : node . Name ,
} ,
}
pod , err := f . ClientSet . Core ( ) . Pods ( f . Namespace . Name ) . Create ( pod )
framework . ExpectNoError ( err )
By ( "Creating statefulset with conflicting port in namespace " + f . Namespace . Name )
2017-02-03 13:41:32 +00:00
ss := newStatefulSet ( ssName , f . Namespace . Name , headlessSvcName , 1 , nil , nil , labels )
statefulPodContainer := & ss . Spec . Template . Spec . Containers [ 0 ]
statefulPodContainer . Ports = append ( statefulPodContainer . Ports , conflictingPort )
ss . Spec . Template . Spec . NodeName = node . Name
_ , err = f . ClientSet . Apps ( ) . StatefulSets ( f . Namespace . Name ) . Create ( ss )
2017-02-01 00:45:59 +00:00
framework . ExpectNoError ( err )
By ( "Waiting until pod " + podName + " will start running in namespace " + f . Namespace . Name )
if err := f . WaitForPodRunning ( podName ) ; err != nil {
framework . Failf ( "Pod %v did not start running: %v" , podName , err )
}
2017-02-03 13:41:32 +00:00
var initialStatefulPodUID types . UID
By ( "Waiting until stateful pod " + statefulPodName + " will be recreated and deleted at least once in namespace " + f . Namespace . Name )
w , err := f . ClientSet . Core ( ) . Pods ( f . Namespace . Name ) . Watch ( metav1 . SingleObject ( metav1 . ObjectMeta { Name : statefulPodName } ) )
2017-02-01 00:45:59 +00:00
framework . ExpectNoError ( err )
// we need to get UID from pod in any state and wait until stateful set controller will remove pod atleast once
2017-02-03 13:41:32 +00:00
_ , err = watch . Until ( statefulPodTimeout , w , func ( event watch . Event ) ( bool , error ) {
2017-02-01 00:45:59 +00:00
pod := event . Object . ( * v1 . Pod )
switch event . Type {
case watch . Deleted :
framework . Logf ( "Observed delete event for stateful pod %v in namespace %v" , pod . Name , pod . Namespace )
2017-02-03 13:41:32 +00:00
if initialStatefulPodUID == "" {
2017-02-01 00:45:59 +00:00
return false , nil
}
return true , nil
}
framework . Logf ( "Observed stateful 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 )
2017-02-03 13:41:32 +00:00
initialStatefulPodUID = pod . UID
2017-02-01 00:45:59 +00:00
return false , nil
} )
if err != nil {
2017-02-03 13:41:32 +00:00
framework . Failf ( "Pod %v expected to be re-created at least once" , statefulPodName )
2017-02-01 00:45:59 +00:00
}
By ( "Removing pod with conflicting port in namespace " + f . Namespace . Name )
2017-02-03 13:41:32 +00:00
err = f . ClientSet . Core ( ) . Pods ( f . Namespace . Name ) . Delete ( pod . Name , metav1 . NewDeleteOptions ( 0 ) )
2017-02-01 00:45:59 +00:00
framework . ExpectNoError ( err )
2017-02-03 13:41:32 +00:00
By ( "Waiting when stateful pod " + statefulPodName + " will be recreated in namespace " + f . Namespace . Name + " and will be in running state" )
2017-02-01 00:45:59 +00:00
// we may catch delete event, thats why we are waiting for running phase like this, and not with watch.Until
Eventually ( func ( ) error {
2017-02-03 13:41:32 +00:00
statefulPod , err := f . ClientSet . Core ( ) . Pods ( f . Namespace . Name ) . Get ( statefulPodName , metav1 . GetOptions { } )
2017-02-01 00:45:59 +00:00
if err != nil {
return err
}
2017-02-03 13:41:32 +00:00
if statefulPod . Status . Phase != v1 . PodRunning {
return fmt . Errorf ( "Pod %v is not in running phase: %v" , statefulPod . Name , statefulPod . Status . Phase )
} else if statefulPod . UID == initialStatefulPodUID {
return fmt . Errorf ( "Pod %v wasn't recreated: %v == %v" , statefulPod . Name , statefulPod . UID , initialStatefulPodUID )
2017-02-01 00:45:59 +00:00
}
return nil
2017-02-03 13:41:32 +00:00
} , statefulPodTimeout , 2 * time . Second ) . Should ( BeNil ( ) )
2017-02-01 00:45:59 +00:00
} )
} )
framework . KubeDescribe ( "Deploy clustered applications [Feature:StatefulSet] [Slow]" , func ( ) {
2017-02-03 13:41:32 +00:00
var sst * statefulSetTester
2017-02-01 00:45:59 +00:00
var appTester * clusterAppTester
BeforeEach ( func ( ) {
2017-02-03 13:41:32 +00:00
sst = & statefulSetTester { c : c }
appTester = & clusterAppTester { tester : sst , ns : ns }
2017-02-01 00:45:59 +00:00
} )
AfterEach ( func ( ) {
if CurrentGinkgoTestDescription ( ) . Failed {
dumpDebugInfo ( c , ns )
}
framework . Logf ( "Deleting all statefulset in ns %v" , ns )
deleteAllStatefulSets ( c , ns )
} )
It ( "should creating a working zookeeper cluster" , func ( ) {
2017-02-03 13:41:32 +00:00
appTester . statefulPod = & zookeeperTester { tester : sst }
2017-02-01 00:45:59 +00:00
appTester . run ( )
} )
It ( "should creating a working redis cluster" , func ( ) {
2017-02-03 13:41:32 +00:00
appTester . statefulPod = & redisTester { tester : sst }
2017-02-01 00:45:59 +00:00
appTester . run ( )
} )
It ( "should creating a working mysql cluster" , func ( ) {
2017-02-03 13:41:32 +00:00
appTester . statefulPod = & mysqlGaleraTester { tester : sst }
2017-02-01 00:45:59 +00:00
appTester . run ( )
} )
It ( "should creating a working CockroachDB cluster" , func ( ) {
2017-02-03 13:41:32 +00:00
appTester . statefulPod = & cockroachDBTester { tester : sst }
2017-02-01 00:45:59 +00:00
appTester . run ( )
} )
} )
} )
func dumpDebugInfo ( c clientset . Interface , ns string ) {
2017-02-03 13:41:32 +00:00
sl , _ := c . Core ( ) . Pods ( ns ) . List ( metav1 . ListOptions { LabelSelector : labels . Everything ( ) . String ( ) } )
for _ , s := range sl . Items {
desc , _ := framework . RunKubectl ( "describe" , "po" , s . Name , fmt . Sprintf ( "--namespace=%v" , ns ) )
framework . Logf ( "\nOutput of kubectl describe %v:\n%v" , s . Name , desc )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
l , _ := framework . RunKubectl ( "logs" , s . Name , fmt . Sprintf ( "--namespace=%v" , ns ) , "--tail=100" )
framework . Logf ( "\nLast 100 log lines of %v:\n%v" , s . Name , l )
2017-02-01 00:45:59 +00:00
}
}
func kubectlExecWithRetries ( args ... string ) ( out string ) {
var err error
for i := 0 ; i < 3 ; i ++ {
if out , err = framework . RunKubectl ( args ... ) ; err == nil {
return
}
framework . Logf ( "Retrying %v:\nerror %v\nstdout %v" , args , err , out )
}
framework . Failf ( "Failed to execute \"%v\" with retries: %v" , args , err )
return
}
2017-02-03 13:41:32 +00:00
type statefulPodTester interface {
2017-02-01 00:45:59 +00:00
deploy ( ns string ) * apps . StatefulSet
2017-02-03 13:41:32 +00:00
write ( statefulPodIndex int , kv map [ string ] string )
read ( statefulPodIndex int , key string ) string
2017-02-01 00:45:59 +00:00
name ( ) string
}
type clusterAppTester struct {
2017-02-03 13:41:32 +00:00
ns string
statefulPod statefulPodTester
tester * statefulSetTester
2017-02-01 00:45:59 +00:00
}
func ( c * clusterAppTester ) run ( ) {
2017-02-03 13:41:32 +00:00
By ( "Deploying " + c . statefulPod . name ( ) )
ss := c . statefulPod . deploy ( c . ns )
2017-02-01 00:45:59 +00:00
By ( "Creating foo:bar in member with index 0" )
2017-02-03 13:41:32 +00:00
c . statefulPod . write ( 0 , map [ string ] string { "foo" : "bar" } )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
switch c . statefulPod . ( type ) {
2017-02-01 00:45:59 +00:00
case * mysqlGaleraTester :
// Don't restart MySQL cluster since it doesn't handle restarts well
default :
if restartCluster {
2017-02-03 13:41:32 +00:00
By ( "Restarting stateful set " + ss . Name )
c . tester . restart ( ss )
c . tester . waitForRunningAndReady ( * ss . Spec . Replicas , ss )
2017-02-01 00:45:59 +00:00
}
}
By ( "Reading value under foo from member with index 2" )
2017-02-03 13:41:32 +00:00
if err := pollReadWithTimeout ( c . statefulPod , 2 , "foo" , "bar" ) ; err != nil {
2017-02-01 00:45:59 +00:00
framework . Failf ( "%v" , err )
}
}
type zookeeperTester struct {
2017-02-03 13:41:32 +00:00
ss * apps . StatefulSet
2017-02-01 00:45:59 +00:00
tester * statefulSetTester
}
func ( z * zookeeperTester ) name ( ) string {
return "zookeeper"
}
func ( z * zookeeperTester ) deploy ( ns string ) * apps . StatefulSet {
2017-02-03 13:41:32 +00:00
z . ss = z . tester . createStatefulSet ( zookeeperManifestPath , ns )
return z . ss
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( z * zookeeperTester ) write ( statefulPodIndex int , kv map [ string ] string ) {
name := fmt . Sprintf ( "%v-%d" , z . ss . Name , statefulPodIndex )
ns := fmt . Sprintf ( "--namespace=%v" , z . ss . Namespace )
2017-02-01 00:45:59 +00:00
for k , v := range kv {
cmd := fmt . Sprintf ( "/opt/zookeeper/bin/zkCli.sh create /%v %v" , k , v )
framework . Logf ( framework . RunKubectlOrDie ( "exec" , ns , name , "--" , "/bin/sh" , "-c" , cmd ) )
}
}
2017-02-03 13:41:32 +00:00
func ( z * zookeeperTester ) read ( statefulPodIndex int , key string ) string {
name := fmt . Sprintf ( "%v-%d" , z . ss . Name , statefulPodIndex )
ns := fmt . Sprintf ( "--namespace=%v" , z . ss . Namespace )
2017-02-01 00:45:59 +00:00
cmd := fmt . Sprintf ( "/opt/zookeeper/bin/zkCli.sh get /%v" , key )
return lastLine ( framework . RunKubectlOrDie ( "exec" , ns , name , "--" , "/bin/sh" , "-c" , cmd ) )
}
type mysqlGaleraTester struct {
2017-02-03 13:41:32 +00:00
ss * apps . StatefulSet
2017-02-01 00:45:59 +00:00
tester * statefulSetTester
}
func ( m * mysqlGaleraTester ) name ( ) string {
return "mysql: galera"
}
func ( m * mysqlGaleraTester ) mysqlExec ( cmd , ns , podName string ) string {
cmd = fmt . Sprintf ( "/usr/bin/mysql -u root -B -e '%v'" , cmd )
// TODO: Find a readiness probe for mysql that guarantees writes will
// succeed and ditch retries. Current probe only reads, so there's a window
// for a race.
return kubectlExecWithRetries ( fmt . Sprintf ( "--namespace=%v" , ns ) , "exec" , podName , "--" , "/bin/sh" , "-c" , cmd )
}
func ( m * mysqlGaleraTester ) deploy ( ns string ) * apps . StatefulSet {
2017-02-03 13:41:32 +00:00
m . ss = m . tester . createStatefulSet ( mysqlGaleraManifestPath , ns )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
framework . Logf ( "Deployed statefulset %v, initializing database" , m . ss . Name )
2017-02-01 00:45:59 +00:00
for _ , cmd := range [ ] string {
"create database statefulset;" ,
"use statefulset; create table foo (k varchar(20), v varchar(20));" ,
} {
2017-02-03 13:41:32 +00:00
framework . Logf ( m . mysqlExec ( cmd , ns , fmt . Sprintf ( "%v-0" , m . ss . Name ) ) )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
return m . ss
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( m * mysqlGaleraTester ) write ( statefulPodIndex int , kv map [ string ] string ) {
name := fmt . Sprintf ( "%v-%d" , m . ss . Name , statefulPodIndex )
2017-02-01 00:45:59 +00:00
for k , v := range kv {
cmd := fmt . Sprintf ( "use statefulset; insert into foo (k, v) values (\"%v\", \"%v\");" , k , v )
2017-02-03 13:41:32 +00:00
framework . Logf ( m . mysqlExec ( cmd , m . ss . Namespace , name ) )
2017-02-01 00:45:59 +00:00
}
}
2017-02-03 13:41:32 +00:00
func ( m * mysqlGaleraTester ) read ( statefulPodIndex int , key string ) string {
name := fmt . Sprintf ( "%v-%d" , m . ss . Name , statefulPodIndex )
return lastLine ( m . mysqlExec ( fmt . Sprintf ( "use statefulset; select v from foo where k=\"%v\";" , key ) , m . ss . Namespace , name ) )
2017-02-01 00:45:59 +00:00
}
type redisTester struct {
2017-02-03 13:41:32 +00:00
ss * apps . StatefulSet
2017-02-01 00:45:59 +00:00
tester * statefulSetTester
}
func ( m * redisTester ) name ( ) string {
return "redis: master/slave"
}
func ( m * redisTester ) redisExec ( cmd , ns , podName string ) string {
cmd = fmt . Sprintf ( "/opt/redis/redis-cli -h %v %v" , podName , cmd )
return framework . RunKubectlOrDie ( fmt . Sprintf ( "--namespace=%v" , ns ) , "exec" , podName , "--" , "/bin/sh" , "-c" , cmd )
}
func ( m * redisTester ) deploy ( ns string ) * apps . StatefulSet {
2017-02-03 13:41:32 +00:00
m . ss = m . tester . createStatefulSet ( redisManifestPath , ns )
return m . ss
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( m * redisTester ) write ( statefulPodIndex int , kv map [ string ] string ) {
name := fmt . Sprintf ( "%v-%d" , m . ss . Name , statefulPodIndex )
2017-02-01 00:45:59 +00:00
for k , v := range kv {
2017-02-03 13:41:32 +00:00
framework . Logf ( m . redisExec ( fmt . Sprintf ( "SET %v %v" , k , v ) , m . ss . Namespace , name ) )
2017-02-01 00:45:59 +00:00
}
}
2017-02-03 13:41:32 +00:00
func ( m * redisTester ) read ( statefulPodIndex int , key string ) string {
name := fmt . Sprintf ( "%v-%d" , m . ss . Name , statefulPodIndex )
return lastLine ( m . redisExec ( fmt . Sprintf ( "GET %v" , key ) , m . ss . Namespace , name ) )
2017-02-01 00:45:59 +00:00
}
type cockroachDBTester struct {
2017-02-03 13:41:32 +00:00
ss * apps . StatefulSet
2017-02-01 00:45:59 +00:00
tester * statefulSetTester
}
func ( c * cockroachDBTester ) name ( ) string {
return "CockroachDB"
}
func ( c * cockroachDBTester ) cockroachDBExec ( cmd , ns , podName string ) string {
cmd = fmt . Sprintf ( "/cockroach/cockroach sql --host %s.cockroachdb -e \"%v\"" , podName , cmd )
return framework . RunKubectlOrDie ( fmt . Sprintf ( "--namespace=%v" , ns ) , "exec" , podName , "--" , "/bin/sh" , "-c" , cmd )
}
func ( c * cockroachDBTester ) deploy ( ns string ) * apps . StatefulSet {
2017-02-03 13:41:32 +00:00
c . ss = c . tester . createStatefulSet ( cockroachDBManifestPath , ns )
framework . Logf ( "Deployed statefulset %v, initializing database" , c . ss . Name )
2017-02-01 00:45:59 +00:00
for _ , cmd := range [ ] string {
"CREATE DATABASE IF NOT EXISTS foo;" ,
"CREATE TABLE IF NOT EXISTS foo.bar (k STRING PRIMARY KEY, v STRING);" ,
} {
2017-02-03 13:41:32 +00:00
framework . Logf ( c . cockroachDBExec ( cmd , ns , fmt . Sprintf ( "%v-0" , c . ss . Name ) ) )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
return c . ss
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( c * cockroachDBTester ) write ( statefulPodIndex int , kv map [ string ] string ) {
name := fmt . Sprintf ( "%v-%d" , c . ss . Name , statefulPodIndex )
2017-02-01 00:45:59 +00:00
for k , v := range kv {
cmd := fmt . Sprintf ( "UPSERT INTO foo.bar VALUES ('%v', '%v');" , k , v )
2017-02-03 13:41:32 +00:00
framework . Logf ( c . cockroachDBExec ( cmd , c . ss . Namespace , name ) )
2017-02-01 00:45:59 +00:00
}
}
2017-02-03 13:41:32 +00:00
func ( c * cockroachDBTester ) read ( statefulPodIndex int , key string ) string {
name := fmt . Sprintf ( "%v-%d" , c . ss . Name , statefulPodIndex )
return lastLine ( c . cockroachDBExec ( fmt . Sprintf ( "SELECT v FROM foo.bar WHERE k='%v';" , key ) , c . ss . Namespace , name ) )
2017-02-01 00:45:59 +00:00
}
func lastLine ( out string ) string {
outLines := strings . Split ( strings . Trim ( out , "\n" ) , "\n" )
return outLines [ len ( outLines ) - 1 ]
}
func statefulSetFromManifest ( fileName , ns string ) * apps . StatefulSet {
2017-02-03 13:41:32 +00:00
var ss apps . StatefulSet
2017-02-01 00:45:59 +00:00
framework . Logf ( "Parsing statefulset from %v" , fileName )
data , err := ioutil . ReadFile ( fileName )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
json , err := utilyaml . ToJSON ( data )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-03 13:41:32 +00:00
Expect ( runtime . DecodeInto ( api . Codecs . UniversalDecoder ( ) , json , & ss ) ) . NotTo ( HaveOccurred ( ) )
ss . Namespace = ns
if ss . Spec . Selector == nil {
ss . Spec . Selector = & metav1 . LabelSelector {
MatchLabels : ss . Spec . Template . Labels ,
2017-02-01 00:45:59 +00:00
}
}
2017-02-03 13:41:32 +00:00
return & ss
2017-02-01 00:45:59 +00:00
}
// statefulSetTester has all methods required to test a single statefulset.
type statefulSetTester struct {
c clientset . Interface
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) createStatefulSet ( manifestPath , ns string ) * apps . StatefulSet {
2017-02-01 00:45:59 +00:00
mkpath := func ( file string ) string {
return filepath . Join ( framework . TestContext . RepoRoot , manifestPath , file )
}
2017-02-03 13:41:32 +00:00
ss := statefulSetFromManifest ( mkpath ( "statefulset.yaml" ) , ns )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
framework . Logf ( fmt . Sprintf ( "creating " + ss . Name + " service" ) )
2017-02-01 00:45:59 +00:00
framework . RunKubectlOrDie ( "create" , "-f" , mkpath ( "service.yaml" ) , fmt . Sprintf ( "--namespace=%v" , ns ) )
2017-02-03 13:41:32 +00:00
framework . Logf ( fmt . Sprintf ( "creating statefulset %v/%v with %d replicas and selector %+v" , ss . Namespace , ss . Name , * ( ss . Spec . Replicas ) , ss . Spec . Selector ) )
framework . RunKubectlOrDie ( "create" , "-f" , mkpath ( "statefulset.yaml" ) , fmt . Sprintf ( "--namespace=%v" , ns ) )
s . waitForRunningAndReady ( * ss . Spec . Replicas , ss )
return ss
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) checkMount ( ss * apps . StatefulSet , mountPath string ) error {
2017-02-01 00:45:59 +00:00
for _ , cmd := range [ ] string {
// Print inode, size etc
fmt . Sprintf ( "ls -idlh %v" , mountPath ) ,
// Print subdirs
fmt . Sprintf ( "find %v" , mountPath ) ,
// Try writing
fmt . Sprintf ( "touch %v" , filepath . Join ( mountPath , fmt . Sprintf ( "%v" , time . Now ( ) . UnixNano ( ) ) ) ) ,
} {
2017-02-03 13:41:32 +00:00
if err := s . execInStatefulPods ( ss , cmd ) ; err != nil {
2017-02-01 00:45:59 +00:00
return fmt . Errorf ( "failed to execute %v, error: %v" , cmd , err )
}
}
return nil
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) execInStatefulPods ( ss * apps . StatefulSet , cmd string ) error {
podList := s . getPodList ( ss )
for _ , statefulPod := range podList . Items {
stdout , err := framework . RunHostCmd ( statefulPod . Namespace , statefulPod . Name , cmd )
framework . Logf ( "stdout of %v on %v: %v" , cmd , statefulPod . Name , stdout )
2017-02-01 00:45:59 +00:00
if err != nil {
return err
}
}
return nil
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) checkHostname ( ss * apps . StatefulSet ) error {
2017-02-01 00:45:59 +00:00
cmd := "printf $(hostname)"
2017-02-03 13:41:32 +00:00
podList := s . getPodList ( ss )
for _ , statefulPod := range podList . Items {
hostname , err := framework . RunHostCmd ( statefulPod . Namespace , statefulPod . Name , cmd )
2017-02-01 00:45:59 +00:00
if err != nil {
return err
}
2017-02-03 13:41:32 +00:00
if hostname != statefulPod . Name {
return fmt . Errorf ( "unexpected hostname (%s) and stateful pod name (%s) not equal" , hostname , statefulPod . Name )
2017-02-01 00:45:59 +00:00
}
}
return nil
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) saturate ( ss * apps . StatefulSet ) {
// TODO: Watch events and check that creation timestamss don't overlap
2017-02-01 00:45:59 +00:00
var i int32
2017-02-03 13:41:32 +00:00
for i = 0 ; i < * ( ss . Spec . Replicas ) ; i ++ {
2017-02-01 00:45:59 +00:00
framework . Logf ( "Waiting for stateful pod at index " + fmt . Sprintf ( "%v" , i + 1 ) + " to enter Running" )
2017-02-03 13:41:32 +00:00
s . waitForRunningAndReady ( i + 1 , ss )
2017-02-01 00:45:59 +00:00
framework . Logf ( "Marking stateful pod at index " + fmt . Sprintf ( "%v" , i ) + " healthy" )
2017-02-03 13:41:32 +00:00
s . setHealthy ( ss )
2017-02-01 00:45:59 +00:00
}
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) deleteStatefulPodAtIndex ( index int , ss * apps . StatefulSet ) {
name := getPodNameAtIndex ( index , ss )
2017-02-01 00:45:59 +00:00
noGrace := int64 ( 0 )
2017-02-03 13:41:32 +00:00
if err := s . c . Core ( ) . Pods ( ss . Namespace ) . Delete ( name , & metav1 . DeleteOptions { GracePeriodSeconds : & noGrace } ) ; err != nil {
framework . Failf ( "Failed to delete stateful pod %v for StatefulSet %v/%v: %v" , name , ss . Namespace , ss . Name , err )
2017-02-01 00:45:59 +00:00
}
}
type verifyPodFunc func ( * v1 . Pod )
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) verifyPodAtIndex ( index int , ss * apps . StatefulSet , verify verifyPodFunc ) {
name := getPodNameAtIndex ( index , ss )
pod , err := s . c . Core ( ) . Pods ( ss . Namespace ) . Get ( name , metav1 . GetOptions { } )
Expect ( err ) . NotTo ( HaveOccurred ( ) , fmt . Sprintf ( "Failed to get stateful pod %s for StatefulSet %s/%s" , name , ss . Namespace , ss . Name ) )
2017-02-01 00:45:59 +00:00
verify ( pod )
}
2017-02-03 13:41:32 +00:00
func getPodNameAtIndex ( index int , ss * apps . StatefulSet ) string {
2017-02-01 00:45:59 +00:00
// TODO: we won't use "-index" as the name strategy forever,
// pull the name out from an identity mapper.
2017-02-03 13:41:32 +00:00
return fmt . Sprintf ( "%v-%v" , ss . Name , index )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) scale ( ss * apps . StatefulSet , count int32 ) error {
name := ss . Name
ns := ss . Namespace
s . update ( ns , name , func ( ss * apps . StatefulSet ) { * ( ss . Spec . Replicas ) = count } )
2017-02-01 00:45:59 +00:00
2017-02-03 13:41:32 +00:00
var statefulPodList * v1 . PodList
2017-02-01 00:45:59 +00:00
pollErr := wait . PollImmediate ( statefulsetPoll , statefulsetTimeout , func ( ) ( bool , error ) {
2017-02-03 13:41:32 +00:00
statefulPodList = s . getPodList ( ss )
if int32 ( len ( statefulPodList . Items ) ) == count {
2017-02-01 00:45:59 +00:00
return true , nil
}
return false , nil
} )
if pollErr != nil {
unhealthy := [ ] string { }
2017-02-03 13:41:32 +00:00
for _ , statefulPod := range statefulPodList . Items {
delTs , phase , readiness := statefulPod . DeletionTimestamp , statefulPod . Status . Phase , v1 . IsPodReady ( & statefulPod )
2017-02-01 00:45:59 +00:00
if delTs != nil || phase != v1 . PodRunning || ! readiness {
2017-02-03 13:41:32 +00:00
unhealthy = append ( unhealthy , fmt . Sprintf ( "%v: deletion %v, phase %v, readiness %v" , statefulPod . Name , delTs , phase , readiness ) )
2017-02-01 00:45:59 +00:00
}
}
return fmt . Errorf ( "Failed to scale statefulset to %d in %v. Remaining pods:\n%v" , count , statefulsetTimeout , unhealthy )
}
return nil
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) updateReplicas ( ss * apps . StatefulSet , count int32 ) {
s . update ( ss . Namespace , ss . Name , func ( ss * apps . StatefulSet ) { ss . Spec . Replicas = & count } )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) restart ( ss * apps . StatefulSet ) {
oldReplicas := * ( ss . Spec . Replicas )
framework . ExpectNoError ( s . scale ( ss , 0 ) )
s . update ( ss . Namespace , ss . Name , func ( ss * apps . StatefulSet ) { * ( ss . Spec . Replicas ) = oldReplicas } )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) update ( ns , name string , update func ( ss * apps . StatefulSet ) ) {
2017-02-01 00:45:59 +00:00
for i := 0 ; i < 3 ; i ++ {
2017-02-03 13:41:32 +00:00
ss , err := s . c . Apps ( ) . StatefulSets ( ns ) . Get ( name , metav1 . GetOptions { } )
2017-02-01 00:45:59 +00:00
if err != nil {
framework . Failf ( "failed to get statefulset %q: %v" , name , err )
}
2017-02-03 13:41:32 +00:00
update ( ss )
ss , err = s . c . Apps ( ) . StatefulSets ( ns ) . Update ( ss )
2017-02-01 00:45:59 +00:00
if err == nil {
return
}
if ! apierrs . IsConflict ( err ) && ! apierrs . IsServerTimeout ( err ) {
framework . Failf ( "failed to update statefulset %q: %v" , name , err )
}
}
framework . Failf ( "too many retries draining statefulset %q" , name )
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) getPodList ( ss * apps . StatefulSet ) * v1 . PodList {
selector , err := metav1 . LabelSelectorAsSelector ( ss . Spec . Selector )
2017-02-01 00:45:59 +00:00
framework . ExpectNoError ( err )
2017-02-03 13:41:32 +00:00
podList , err := s . c . Core ( ) . Pods ( ss . Namespace ) . List ( metav1 . ListOptions { LabelSelector : selector . String ( ) } )
2017-02-01 00:45:59 +00:00
framework . ExpectNoError ( err )
return podList
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) confirmStatefulPodCount ( count int , ss * apps . StatefulSet , timeout time . Duration ) {
2017-02-01 00:45:59 +00:00
start := time . Now ( )
deadline := start . Add ( timeout )
for t := time . Now ( ) ; t . Before ( deadline ) ; t = time . Now ( ) {
2017-02-03 13:41:32 +00:00
podList := s . getPodList ( ss )
statefulPodCount := len ( podList . Items )
if statefulPodCount != count {
framework . Failf ( "StatefulSet %v scaled unexpectedly scaled to %d -> %d replicas: %+v" , ss . Name , count , len ( podList . Items ) , podList )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
framework . Logf ( "Verifying statefulset %v doesn't scale past %d for another %+v" , ss . Name , count , deadline . Sub ( t ) )
2017-02-01 00:45:59 +00:00
time . Sleep ( 1 * time . Second )
}
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) waitForRunning ( numStatefulPods int32 , ss * apps . StatefulSet , shouldBeReady bool ) {
2017-02-01 00:45:59 +00:00
pollErr := wait . PollImmediate ( statefulsetPoll , statefulsetTimeout ,
func ( ) ( bool , error ) {
2017-02-03 13:41:32 +00:00
podList := s . getPodList ( ss )
if int32 ( len ( podList . Items ) ) < numStatefulPods {
framework . Logf ( "Found %d stateful pods, waiting for %d" , len ( podList . Items ) , numStatefulPods )
2017-02-01 00:45:59 +00:00
return false , nil
}
2017-02-03 13:41:32 +00:00
if int32 ( len ( podList . Items ) ) > numStatefulPods {
return false , fmt . Errorf ( "Too many pods scheduled, expected %d got %d" , numStatefulPods , len ( podList . Items ) )
2017-02-01 00:45:59 +00:00
}
for _ , p := range podList . Items {
isReady := v1 . IsPodReady ( & p )
desiredReadiness := shouldBeReady == isReady
framework . Logf ( "Waiting for pod %v to enter %v - Ready=%v, currently %v - Ready=%v" , p . Name , v1 . PodRunning , shouldBeReady , p . Status . Phase , isReady )
if p . Status . Phase != v1 . PodRunning || ! desiredReadiness {
return false , nil
}
}
return true , nil
} )
if pollErr != nil {
framework . Failf ( "Failed waiting for pods to enter running: %v" , pollErr )
}
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) waitForRunningAndReady ( numStatefulPods int32 , ss * apps . StatefulSet ) {
s . waitForRunning ( numStatefulPods , ss , true )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) waitForRunningAndNotReady ( numStatefulPods int32 , ss * apps . StatefulSet ) {
s . waitForRunning ( numStatefulPods , ss , false )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) breakProbe ( ss * apps . StatefulSet , probe * v1 . Probe ) error {
2017-02-01 00:45:59 +00:00
path := probe . HTTPGet . Path
if path == "" {
return fmt . Errorf ( "Path expected to be not empty: %v" , path )
}
cmd := fmt . Sprintf ( "mv -v /usr/share/nginx/html%v /tmp/" , path )
2017-02-03 13:41:32 +00:00
return s . execInStatefulPods ( ss , cmd )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) restoreProbe ( ss * apps . StatefulSet , probe * v1 . Probe ) error {
2017-02-01 00:45:59 +00:00
path := probe . HTTPGet . Path
if path == "" {
return fmt . Errorf ( "Path expected to be not empty: %v" , path )
}
cmd := fmt . Sprintf ( "mv -v /tmp%v /usr/share/nginx/html/" , path )
2017-02-03 13:41:32 +00:00
return s . execInStatefulPods ( ss , cmd )
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) setHealthy ( ss * apps . StatefulSet ) {
podList := s . getPodList ( ss )
2017-02-01 00:45:59 +00:00
markedHealthyPod := ""
for _ , pod := range podList . Items {
if pod . Status . Phase != v1 . PodRunning {
framework . Failf ( "Found pod in %v cannot set health" , pod . Status . Phase )
}
if isInitialized ( pod ) {
continue
}
if markedHealthyPod != "" {
framework . Failf ( "Found multiple non-healthy stateful pods: %v and %v" , pod . Name , markedHealthyPod )
}
2017-02-03 13:41:32 +00:00
p , err := framework . UpdatePodWithRetries ( s . c , pod . Namespace , pod . Name , func ( update * v1 . Pod ) {
update . Annotations [ statefulset . StatefulSetInitAnnotation ] = "true"
2017-02-01 00:45:59 +00:00
} )
framework . ExpectNoError ( err )
2017-02-03 13:41:32 +00:00
framework . Logf ( "Set annotation %v to %v on pod %v" , statefulset . StatefulSetInitAnnotation , p . Annotations [ statefulset . StatefulSetInitAnnotation ] , pod . Name )
2017-02-01 00:45:59 +00:00
markedHealthyPod = pod . Name
}
}
2017-02-03 13:41:32 +00:00
func ( s * statefulSetTester ) waitForStatus ( ss * apps . StatefulSet , expectedReplicas int32 ) {
2017-02-01 00:45:59 +00:00
framework . Logf ( "Waiting for statefulset status.replicas updated to %d" , expectedReplicas )
2017-02-03 13:41:32 +00:00
ns , name := ss . Namespace , ss . Name
2017-02-01 00:45:59 +00:00
pollErr := wait . PollImmediate ( statefulsetPoll , statefulsetTimeout ,
func ( ) ( bool , error ) {
2017-02-03 13:41:32 +00:00
ssGet , err := s . c . Apps ( ) . StatefulSets ( ns ) . Get ( name , metav1 . GetOptions { } )
2017-02-01 00:45:59 +00:00
if err != nil {
return false , err
}
2017-02-03 13:41:32 +00:00
if ssGet . Status . Replicas != expectedReplicas {
framework . Logf ( "Waiting for stateful set status to become %d, currently %d" , expectedReplicas , ssGet . Status . Replicas )
2017-02-01 00:45:59 +00:00
return false , nil
}
return true , nil
} )
if pollErr != nil {
framework . Failf ( "Failed waiting for stateful set status.replicas updated to %d: %v" , expectedReplicas , pollErr )
}
}
func deleteAllStatefulSets ( c clientset . Interface , ns string ) {
2017-02-03 13:41:32 +00:00
sst := & statefulSetTester { c : c }
ssList , err := c . Apps ( ) . StatefulSets ( ns ) . List ( metav1 . ListOptions { LabelSelector : labels . Everything ( ) . String ( ) } )
2017-02-01 00:45:59 +00:00
framework . ExpectNoError ( err )
// Scale down each statefulset, then delete it completely.
// Deleting a pvc without doing this will leak volumes, #25101.
errList := [ ] string { }
2017-02-03 13:41:32 +00:00
for _ , ss := range ssList . Items {
framework . Logf ( "Scaling statefulset %v to 0" , ss . Name )
if err := sst . scale ( & ss , 0 ) ; err != nil {
2017-02-01 00:45:59 +00:00
errList = append ( errList , fmt . Sprintf ( "%v" , err ) )
}
2017-02-03 13:41:32 +00:00
sst . waitForStatus ( & ss , 0 )
framework . Logf ( "Deleting statefulset %v" , ss . Name )
if err := c . Apps ( ) . StatefulSets ( ss . Namespace ) . Delete ( ss . Name , nil ) ; err != nil {
2017-02-01 00:45:59 +00:00
errList = append ( errList , fmt . Sprintf ( "%v" , err ) )
}
}
// pvs are global, so we need to wait for the exact ones bound to the statefulset pvcs.
pvNames := sets . NewString ( )
// TODO: Don't assume all pvcs in the ns belong to a statefulset
pvcPollErr := wait . PollImmediate ( statefulsetPoll , statefulsetTimeout , func ( ) ( bool , error ) {
2017-02-03 13:41:32 +00:00
pvcList , err := c . Core ( ) . PersistentVolumeClaims ( ns ) . List ( metav1 . ListOptions { LabelSelector : labels . Everything ( ) . String ( ) } )
2017-02-01 00:45:59 +00:00
if err != nil {
framework . Logf ( "WARNING: Failed to list pvcs, retrying %v" , err )
return false , nil
}
for _ , pvc := range pvcList . Items {
pvNames . Insert ( pvc . Spec . VolumeName )
// TODO: Double check that there are no pods referencing the pvc
framework . Logf ( "Deleting pvc: %v with volume %v" , pvc . Name , pvc . Spec . VolumeName )
if err := c . Core ( ) . PersistentVolumeClaims ( ns ) . Delete ( pvc . Name , nil ) ; err != nil {
return false , nil
}
}
return true , nil
} )
if pvcPollErr != nil {
errList = append ( errList , fmt . Sprintf ( "Timeout waiting for pvc deletion." ) )
}
pollErr := wait . PollImmediate ( statefulsetPoll , statefulsetTimeout , func ( ) ( bool , error ) {
2017-02-03 13:41:32 +00:00
pvList , err := c . Core ( ) . PersistentVolumes ( ) . List ( metav1 . ListOptions { LabelSelector : labels . Everything ( ) . String ( ) } )
2017-02-01 00:45:59 +00:00
if err != nil {
framework . Logf ( "WARNING: Failed to list pvs, retrying %v" , err )
return false , nil
}
waitingFor := [ ] string { }
for _ , pv := range pvList . Items {
if pvNames . Has ( pv . Name ) {
waitingFor = append ( waitingFor , fmt . Sprintf ( "%v: %+v" , pv . Name , pv . Status ) )
}
}
if len ( waitingFor ) == 0 {
return true , nil
}
framework . Logf ( "Still waiting for pvs of statefulset to disappear:\n%v" , strings . Join ( waitingFor , "\n" ) )
return false , nil
} )
if pollErr != nil {
errList = append ( errList , fmt . Sprintf ( "Timeout waiting for pv provisioner to delete pvs, this might mean the test leaked pvs." ) )
}
if len ( errList ) != 0 {
framework . ExpectNoError ( fmt . Errorf ( "%v" , strings . Join ( errList , "\n" ) ) )
}
}
2017-02-03 13:41:32 +00:00
func pollReadWithTimeout ( statefulPod statefulPodTester , statefulPodNumber int , key , expectedVal string ) error {
2017-02-01 00:45:59 +00:00
err := wait . PollImmediate ( time . Second , readTimeout , func ( ) ( bool , error ) {
2017-02-03 13:41:32 +00:00
val := statefulPod . read ( statefulPodNumber , key )
2017-02-01 00:45:59 +00:00
if val == "" {
return false , nil
} else if val != expectedVal {
return false , fmt . Errorf ( "expected value %v, found %v" , expectedVal , val )
}
return true , nil
} )
if err == wait . ErrWaitTimeout {
2017-02-03 13:41:32 +00:00
return fmt . Errorf ( "timed out when trying to read value for key %v from stateful pod %d" , key , statefulPodNumber )
2017-02-01 00:45:59 +00:00
}
return err
}
func isInitialized ( pod v1 . Pod ) bool {
2017-02-03 13:41:32 +00:00
initialized , ok := pod . Annotations [ statefulset . StatefulSetInitAnnotation ]
2017-02-01 00:45:59 +00:00
if ! ok {
return false
}
inited , err := strconv . ParseBool ( initialized )
if err != nil {
framework . Failf ( "Couldn't parse statefulset init annotations %v" , initialized )
}
return inited
}
func dec ( i int64 , exponent int ) * inf . Dec {
return inf . NewDec ( i , inf . Scale ( - exponent ) )
}
func newPVC ( name string ) v1 . PersistentVolumeClaim {
return v1 . PersistentVolumeClaim {
2017-02-03 13:41:32 +00:00
ObjectMeta : metav1 . ObjectMeta {
2017-02-01 00:45:59 +00:00
Name : name ,
Annotations : map [ string ] string {
"volume.alpha.kubernetes.io/storage-class" : "anything" ,
} ,
} ,
Spec : v1 . PersistentVolumeClaimSpec {
AccessModes : [ ] v1 . PersistentVolumeAccessMode {
v1 . ReadWriteOnce ,
} ,
Resources : v1 . ResourceRequirements {
Requests : v1 . ResourceList {
v1 . ResourceStorage : * resource . NewQuantity ( 1 , resource . BinarySI ) ,
} ,
} ,
} ,
}
}
2017-02-03 13:41:32 +00:00
func newStatefulSet ( name , ns , governingSvcName string , replicas int32 , statefulPodMounts [ ] v1 . VolumeMount , podMounts [ ] v1 . VolumeMount , labels map [ string ] string ) * apps . StatefulSet {
mounts := append ( statefulPodMounts , podMounts ... )
2017-02-01 00:45:59 +00:00
claims := [ ] v1 . PersistentVolumeClaim { }
2017-02-03 13:41:32 +00:00
for _ , m := range statefulPodMounts {
2017-02-01 00:45:59 +00:00
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" ,
} ,
2017-02-03 13:41:32 +00:00
ObjectMeta : metav1 . ObjectMeta {
2017-02-01 00:45:59 +00:00
Name : name ,
Namespace : ns ,
} ,
Spec : apps . StatefulSetSpec {
Selector : & metav1 . LabelSelector {
MatchLabels : labels ,
} ,
Replicas : func ( i int32 ) * int32 { return & i } ( replicas ) ,
Template : v1 . PodTemplateSpec {
2017-02-03 13:41:32 +00:00
ObjectMeta : metav1 . ObjectMeta {
2017-02-01 00:45:59 +00:00
Labels : labels ,
Annotations : map [ string ] string { } ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "nginx" ,
Image : nginxImage ,
VolumeMounts : mounts ,
} ,
} ,
Volumes : vols ,
} ,
} ,
VolumeClaimTemplates : claims ,
ServiceName : governingSvcName ,
} ,
}
}
func setInitializedAnnotation ( ss * apps . StatefulSet , value string ) {
ss . Spec . Template . ObjectMeta . Annotations [ "pod.alpha.kubernetes.io/initialized" ] = value
}