2015-05-29 20:34:02 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2015 The Kubernetes Authors .
2015-05-29 20:34:02 +00:00
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 volume
import (
"fmt"
2017-02-03 05:57:20 +00:00
"hash/fnv"
2015-09-17 22:21:55 +00:00
"strings"
2015-05-29 20:34:02 +00:00
"testing"
2017-01-13 17:48:50 +00:00
"k8s.io/apimachinery/pkg/api/errors"
2017-01-25 13:13:07 +00:00
"k8s.io/apimachinery/pkg/api/resource"
2017-01-17 03:38:19 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2017-02-03 05:57:20 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/watch"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
2016-11-18 20:58:56 +00:00
"k8s.io/kubernetes/pkg/api/v1"
2015-05-29 20:34:02 +00:00
)
2016-09-08 10:57:57 +00:00
type testcase struct {
// Input of the test
name string
2016-11-18 20:58:56 +00:00
existingPod * v1 . Pod
createPod * v1 . Pod
2016-09-08 10:57:57 +00:00
// eventSequence is list of events that are simulated during recycling. It
// can be either event generated by a recycler pod or a state change of
// the pod. (see newPodEvent and newEvent below).
eventSequence [ ] watch . Event
2015-05-29 20:34:02 +00:00
2016-09-08 10:57:57 +00:00
// Expected output.
// expectedEvents is list of events that were sent to the volume that was
// recycled.
expectedEvents [ ] mockEvent
expectedError string
2015-05-29 20:34:02 +00:00
}
2016-11-18 20:58:56 +00:00
func newPodEvent ( eventtype watch . EventType , name string , phase v1 . PodPhase , message string ) watch . Event {
2016-09-08 10:57:57 +00:00
return watch . Event {
Type : eventtype ,
Object : newPod ( name , phase , message ) ,
2015-05-29 20:34:02 +00:00
}
2016-09-08 10:57:57 +00:00
}
2015-05-29 20:34:02 +00:00
2016-09-08 10:57:57 +00:00
func newEvent ( eventtype , message string ) watch . Event {
return watch . Event {
Type : watch . Added ,
2016-11-18 20:58:56 +00:00
Object : & v1 . Event {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2017-01-22 03:36:02 +00:00
Namespace : metav1 . NamespaceDefault ,
2016-09-08 10:57:57 +00:00
} ,
Reason : "MockEvent" ,
Message : message ,
Type : eventtype ,
} ,
2015-05-29 20:34:02 +00:00
}
}
2016-11-18 20:58:56 +00:00
func newPod ( name string , phase v1 . PodPhase , message string ) * v1 . Pod {
return & v1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2017-01-22 03:36:02 +00:00
Namespace : metav1 . NamespaceDefault ,
2016-09-08 10:57:57 +00:00
Name : name ,
2016-05-19 10:58:25 +00:00
} ,
2016-11-18 20:58:56 +00:00
Status : v1 . PodStatus {
2016-09-08 10:57:57 +00:00
Phase : phase ,
Message : message ,
2016-05-19 10:58:25 +00:00
} ,
}
2016-09-08 10:57:57 +00:00
}
2016-05-19 10:58:25 +00:00
2016-09-08 10:57:57 +00:00
func TestRecyclerPod ( t * testing . T ) {
tests := [ ] testcase {
{
// Test recycler success with some events
name : "RecyclerSuccess" ,
2016-11-18 20:58:56 +00:00
createPod : newPod ( "podRecyclerSuccess" , v1 . PodPending , "" ) ,
2016-09-08 10:57:57 +00:00
eventSequence : [ ] watch . Event {
// Pod gets Running and Succeeded
2016-11-18 20:58:56 +00:00
newPodEvent ( watch . Added , "podRecyclerSuccess" , v1 . PodPending , "" ) ,
newEvent ( v1 . EventTypeNormal , "Successfully assigned recycler-for-podRecyclerSuccess to 127.0.0.1" ) ,
newEvent ( v1 . EventTypeNormal , "pulling image \"gcr.io/google_containers/busybox\"" ) ,
newEvent ( v1 . EventTypeNormal , "Successfully pulled image \"gcr.io/google_containers/busybox\"" ) ,
newEvent ( v1 . EventTypeNormal , "Created container with docker id 83d929aeac82" ) ,
newEvent ( v1 . EventTypeNormal , "Started container with docker id 83d929aeac82" ) ,
newPodEvent ( watch . Modified , "podRecyclerSuccess" , v1 . PodRunning , "" ) ,
newPodEvent ( watch . Modified , "podRecyclerSuccess" , v1 . PodSucceeded , "" ) ,
2016-09-08 10:57:57 +00:00
} ,
expectedEvents : [ ] mockEvent {
2016-11-18 20:58:56 +00:00
{ v1 . EventTypeNormal , "Successfully assigned recycler-for-podRecyclerSuccess to 127.0.0.1" } ,
{ v1 . EventTypeNormal , "pulling image \"gcr.io/google_containers/busybox\"" } ,
{ v1 . EventTypeNormal , "Successfully pulled image \"gcr.io/google_containers/busybox\"" } ,
{ v1 . EventTypeNormal , "Created container with docker id 83d929aeac82" } ,
{ v1 . EventTypeNormal , "Started container with docker id 83d929aeac82" } ,
2016-09-08 10:57:57 +00:00
} ,
expectedError : "" ,
2016-05-19 10:58:25 +00:00
} ,
2016-09-08 10:57:57 +00:00
{
// Test recycler failure with some events
name : "RecyclerFailure" ,
2016-11-18 20:58:56 +00:00
createPod : newPod ( "podRecyclerFailure" , v1 . PodPending , "" ) ,
2016-09-08 10:57:57 +00:00
eventSequence : [ ] watch . Event {
// Pod gets Running and Succeeded
2016-11-18 20:58:56 +00:00
newPodEvent ( watch . Added , "podRecyclerFailure" , v1 . PodPending , "" ) ,
newEvent ( v1 . EventTypeNormal , "Successfully assigned recycler-for-podRecyclerFailure to 127.0.0.1" ) ,
newEvent ( v1 . EventTypeWarning , "Unable to mount volumes for pod \"recycler-for-podRecyclerFailure_default(3c9809e5-347c-11e6-a79b-3c970e965218)\": timeout expired waiting for volumes to attach/mount" ) ,
2016-12-01 10:22:55 +00:00
newEvent ( v1 . EventTypeWarning , "Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod \"default\"/\"recycler-for-podRecyclerFailure\". list of unattached/unmounted" ) ,
2016-11-18 20:58:56 +00:00
newPodEvent ( watch . Modified , "podRecyclerFailure" , v1 . PodRunning , "" ) ,
newPodEvent ( watch . Modified , "podRecyclerFailure" , v1 . PodFailed , "Pod was active on the node longer than specified deadline" ) ,
2016-09-08 10:57:57 +00:00
} ,
expectedEvents : [ ] mockEvent {
2016-11-18 20:58:56 +00:00
{ v1 . EventTypeNormal , "Successfully assigned recycler-for-podRecyclerFailure to 127.0.0.1" } ,
{ v1 . EventTypeWarning , "Unable to mount volumes for pod \"recycler-for-podRecyclerFailure_default(3c9809e5-347c-11e6-a79b-3c970e965218)\": timeout expired waiting for volumes to attach/mount" } ,
2016-12-01 10:22:55 +00:00
{ v1 . EventTypeWarning , "Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod \"default\"/\"recycler-for-podRecyclerFailure\". list of unattached/unmounted" } ,
2016-09-08 10:57:57 +00:00
} ,
expectedError : "Pod was active on the node longer than specified deadline" ,
} ,
{
// Recycler pod gets deleted
name : "RecyclerDeleted" ,
2016-11-18 20:58:56 +00:00
createPod : newPod ( "podRecyclerDeleted" , v1 . PodPending , "" ) ,
2016-09-08 10:57:57 +00:00
eventSequence : [ ] watch . Event {
// Pod gets Running and Succeeded
2016-11-18 20:58:56 +00:00
newPodEvent ( watch . Added , "podRecyclerDeleted" , v1 . PodPending , "" ) ,
newEvent ( v1 . EventTypeNormal , "Successfully assigned recycler-for-podRecyclerDeleted to 127.0.0.1" ) ,
newPodEvent ( watch . Deleted , "podRecyclerDeleted" , v1 . PodPending , "" ) ,
2016-09-08 10:57:57 +00:00
} ,
expectedEvents : [ ] mockEvent {
2016-11-18 20:58:56 +00:00
{ v1 . EventTypeNormal , "Successfully assigned recycler-for-podRecyclerDeleted to 127.0.0.1" } ,
2016-09-08 10:57:57 +00:00
} ,
expectedError : "recycler pod was deleted" ,
} ,
{
// Another recycler pod is already running
name : "RecyclerRunning" ,
2016-11-18 20:58:56 +00:00
existingPod : newPod ( "podOldRecycler" , v1 . PodRunning , "" ) ,
createPod : newPod ( "podNewRecycler" , v1 . PodFailed , "mock message" ) ,
2016-09-08 10:57:57 +00:00
eventSequence : [ ] watch . Event {
// Old pod succeeds
2016-11-18 20:58:56 +00:00
newPodEvent ( watch . Modified , "podOldRecycler" , v1 . PodSucceeded , "" ) ,
2016-09-08 10:57:57 +00:00
} ,
// No error = old pod succeeded. If the new pod was used, there
// would be error with "mock message".
expectedError : "" ,
} ,
{
// Another recycler pod is already running and fails
name : "FailedRecyclerRunning" ,
2016-11-18 20:58:56 +00:00
existingPod : newPod ( "podOldRecycler" , v1 . PodRunning , "" ) ,
createPod : newPod ( "podNewRecycler" , v1 . PodFailed , "mock message" ) ,
2016-09-08 10:57:57 +00:00
eventSequence : [ ] watch . Event {
// Old pod failure
2016-11-18 20:58:56 +00:00
newPodEvent ( watch . Modified , "podOldRecycler" , v1 . PodFailed , "Pod was active on the node longer than specified deadline" ) ,
2016-09-08 10:57:57 +00:00
} ,
// If the new pod was used, there would be error with "mock message".
expectedError : "Pod was active on the node longer than specified deadline" ,
2016-05-19 10:58:25 +00:00
} ,
}
2016-09-08 10:57:57 +00:00
for _ , test := range tests {
t . Logf ( "Test %q" , test . name )
client := & mockRecyclerClient {
events : test . eventSequence ,
pod : test . existingPod ,
}
err := internalRecycleVolumeByWatchingPodUntilCompletion ( test . createPod . Name , test . createPod , client )
receivedError := ""
if err != nil {
receivedError = err . Error ( )
}
if receivedError != test . expectedError {
t . Errorf ( "Test %q failed, expected error %q, got %q" , test . name , test . expectedError , receivedError )
continue
}
if ! client . deletedCalled {
t . Errorf ( "Test %q failed, expected deferred client.Delete to be called on recycler pod" , test . name )
continue
}
for i , expectedEvent := range test . expectedEvents {
if len ( client . receivedEvents ) <= i {
t . Errorf ( "Test %q failed, expected event %d: %q not received" , test . name , i , expectedEvent . message )
continue
}
receivedEvent := client . receivedEvents [ i ]
if expectedEvent . eventtype != receivedEvent . eventtype {
t . Errorf ( "Test %q failed, event %d does not match: expected eventtype %q, got %q" , test . name , i , expectedEvent . eventtype , receivedEvent . eventtype )
}
if expectedEvent . message != receivedEvent . message {
t . Errorf ( "Test %q failed, event %d does not match: expected message %q, got %q" , test . name , i , expectedEvent . message , receivedEvent . message )
}
}
for i := len ( test . expectedEvents ) ; i < len ( client . receivedEvents ) ; i ++ {
t . Errorf ( "Test %q failed, unexpected event received: %s, %q" , test . name , client . receivedEvents [ i ] . eventtype , client . receivedEvents [ i ] . message )
2016-05-19 10:58:25 +00:00
}
}
}
2015-09-03 03:14:26 +00:00
type mockRecyclerClient struct {
2016-11-18 20:58:56 +00:00
pod * v1 . Pod
2016-09-08 10:57:57 +00:00
deletedCalled bool
receivedEvents [ ] mockEvent
events [ ] watch . Event
}
type mockEvent struct {
eventtype , message string
2015-05-29 20:34:02 +00:00
}
2016-11-18 20:58:56 +00:00
func ( c * mockRecyclerClient ) CreatePod ( pod * v1 . Pod ) ( * v1 . Pod , error ) {
2016-05-19 10:58:25 +00:00
if c . pod == nil {
c . pod = pod
return c . pod , nil
}
// Simulate "already exists" error
return nil , errors . NewAlreadyExists ( api . Resource ( "pods" ) , pod . Name )
2015-05-29 20:34:02 +00:00
}
2016-11-18 20:58:56 +00:00
func ( c * mockRecyclerClient ) GetPod ( name , namespace string ) ( * v1 . Pod , error ) {
2015-05-29 20:34:02 +00:00
if c . pod != nil {
return c . pod , nil
} else {
return nil , fmt . Errorf ( "pod does not exist" )
}
}
2015-09-03 03:14:26 +00:00
func ( c * mockRecyclerClient ) DeletePod ( name , namespace string ) error {
2015-05-29 20:34:02 +00:00
c . deletedCalled = true
return nil
}
2016-09-08 10:57:57 +00:00
func ( c * mockRecyclerClient ) WatchPod ( name , namespace string , stopChannel chan struct { } ) ( <- chan watch . Event , error ) {
eventCh := make ( chan watch . Event , 0 )
go func ( ) {
for _ , e := range c . events {
eventCh <- e
}
} ( )
return eventCh , nil
}
func ( c * mockRecyclerClient ) Event ( eventtype , message string ) {
c . receivedEvents = append ( c . receivedEvents , mockEvent { eventtype , message } )
2015-05-29 20:34:02 +00:00
}
2015-09-03 03:14:26 +00:00
func TestCalculateTimeoutForVolume ( t * testing . T ) {
2016-11-18 20:58:56 +00:00
pv := & v1 . PersistentVolume {
Spec : v1 . PersistentVolumeSpec {
Capacity : v1 . ResourceList {
v1 . ResourceName ( v1 . ResourceStorage ) : resource . MustParse ( "500M" ) ,
2015-09-03 03:14:26 +00:00
} ,
} ,
}
timeout := CalculateTimeoutForVolume ( 50 , 30 , pv )
if timeout != 50 {
t . Errorf ( "Expected 50 for timeout but got %v" , timeout )
}
2016-11-18 20:58:56 +00:00
pv . Spec . Capacity [ v1 . ResourceStorage ] = resource . MustParse ( "2Gi" )
2015-09-03 03:14:26 +00:00
timeout = CalculateTimeoutForVolume ( 50 , 30 , pv )
if timeout != 60 {
t . Errorf ( "Expected 60 for timeout but got %v" , timeout )
}
2016-11-18 20:58:56 +00:00
pv . Spec . Capacity [ v1 . ResourceStorage ] = resource . MustParse ( "150Gi" )
2015-09-03 03:14:26 +00:00
timeout = CalculateTimeoutForVolume ( 50 , 30 , pv )
if timeout != 4500 {
t . Errorf ( "Expected 4500 for timeout but got %v" , timeout )
}
}
2016-02-12 08:46:59 +00:00
func TestGenerateVolumeName ( t * testing . T ) {
// Normal operation, no truncate
v1 := GenerateVolumeName ( "kubernetes" , "pv-cinder-abcde" , 255 )
if v1 != "kubernetes-dynamic-pv-cinder-abcde" {
t . Errorf ( "Expected kubernetes-dynamic-pv-cinder-abcde, got %s" , v1 )
}
// Truncate trailing "6789-dynamic"
prefix := strings . Repeat ( "0123456789" , 9 ) // 90 characters prefix + 8 chars. of "-dynamic"
v2 := GenerateVolumeName ( prefix , "pv-cinder-abcde" , 100 )
expect := prefix [ : 84 ] + "-pv-cinder-abcde"
if v2 != expect {
t . Errorf ( "Expected %s, got %s" , expect , v2 )
}
// Truncate really long cluster name
prefix = strings . Repeat ( "0123456789" , 1000 ) // 10000 characters prefix
v3 := GenerateVolumeName ( prefix , "pv-cinder-abcde" , 100 )
if v3 != expect {
t . Errorf ( "Expected %s, got %s" , expect , v3 )
}
}
2017-02-03 05:57:20 +00:00
func checkFnv32 ( t * testing . T , s string , expected int ) {
h := fnv . New32 ( )
h . Write ( [ ] byte ( s ) )
h . Sum32 ( )
if int ( h . Sum32 ( ) ) != expected {
t . Fatalf ( "hash of %q was %v, expected %v" , s , h . Sum32 ( ) , expected )
}
}
func TestChooseZoneForVolume ( t * testing . T ) {
checkFnv32 ( t , "henley" , 1180403676 )
// 1180403676 mod 3 == 0, so the offset from "henley" is 0, which makes it easier to verify this by inspection
// A few others
checkFnv32 ( t , "henley-" , 2652299129 )
checkFnv32 ( t , "henley-a" , 1459735322 )
checkFnv32 ( t , "" , 2166136261 )
tests := [ ] struct {
Zones [ ] string
VolumeName string
Expected string
} {
// Test for PVC names that don't have a dash
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "henley" ,
Expected : "a" , // hash("henley") == 0
} ,
// Tests for PVC names that end in - number, but don't look like statefulset PVCs
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "henley-0" ,
Expected : "a" , // hash("henley") == 0
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "henley-1" ,
Expected : "b" , // hash("henley") + 1 == 1
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "henley-2" ,
Expected : "c" , // hash("henley") + 2 == 2
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "henley-3" ,
Expected : "a" , // hash("henley") + 3 == 3 === 0 mod 3
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "henley-4" ,
Expected : "b" , // hash("henley") + 4 == 4 === 1 mod 3
} ,
// Tests for PVC names that are edge cases
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "henley-" ,
Expected : "c" , // hash("henley-") = 2652299129 === 2 mod 3
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "henley-a" ,
Expected : "c" , // hash("henley-a") = 1459735322 === 2 mod 3
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium--1" ,
Expected : "c" , // hash("") + 1 == 2166136261 + 1 === 2 mod 3
} ,
// Tests for PVC names for simple StatefulSet cases
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-henley-1" ,
Expected : "b" , // hash("henley") + 1 == 1
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "loud-henley-1" ,
Expected : "b" , // hash("henley") + 1 == 1
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "quiet-henley-2" ,
Expected : "c" , // hash("henley") + 2 == 2
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-henley-2" ,
Expected : "c" , // hash("henley") + 2 == 2
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-henley-3" ,
Expected : "a" , // hash("henley") + 3 == 3 === 0 mod 3
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-henley-4" ,
Expected : "b" , // hash("henley") + 4 == 4 === 1 mod 3
} ,
// Tests for statefulsets (or claims) with dashes in the names
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-alpha-henley-2" ,
Expected : "c" , // hash("henley") + 2 == 2
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-beta-henley-3" ,
Expected : "a" , // hash("henley") + 3 == 3 === 0 mod 3
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-gamma-henley-4" ,
Expected : "b" , // hash("henley") + 4 == 4 === 1 mod 3
} ,
// Tests for statefulsets name ending in -
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-henley--2" ,
Expected : "a" , // hash("") + 2 == 0 mod 3
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-henley--3" ,
Expected : "b" , // hash("") + 3 == 1 mod 3
} ,
{
Zones : [ ] string { "a" , "b" , "c" } ,
VolumeName : "medium-henley--4" ,
Expected : "c" , // hash("") + 4 == 2 mod 3
} ,
}
for _ , test := range tests {
zonesSet := sets . NewString ( test . Zones ... )
actual := ChooseZoneForVolume ( zonesSet , test . VolumeName )
for actual != test . Expected {
t . Errorf ( "Test %v failed, expected zone %q, actual %q" , test , test . Expected , actual )
}
}
}