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"
2017-02-21 18:19:48 +00:00
"reflect"
2015-09-17 22:21:55 +00:00
"strings"
2015-05-29 20:34:02 +00:00
"testing"
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
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"
2017-11-08 22:34:54 +00:00
api "k8s.io/kubernetes/pkg/apis/core"
2017-02-21 18:19:48 +00:00
"k8s.io/kubernetes/pkg/util/slice"
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" ) ,
2017-12-22 22:36:16 +00:00
newEvent ( v1 . EventTypeNormal , "pulling image \"gcr.io/google_containers/busybox\"" ) ,
newEvent ( v1 . EventTypeNormal , "Successfully pulled image \"gcr.io/google_containers/busybox\"" ) ,
2016-11-18 20:58:56 +00:00
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" } ,
2017-12-22 22:36:16 +00:00
{ v1 . EventTypeNormal , "pulling image \"gcr.io/google_containers/busybox\"" } ,
{ v1 . EventTypeNormal , "Successfully pulled image \"gcr.io/google_containers/busybox\"" } ,
2016-11-18 20:58:56 +00:00
{ 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
} ,
2017-10-05 13:08:08 +00:00
expectedError : "failed to recycle volume: Pod was active on the node longer than specified deadline" ,
2016-09-08 10:57:57 +00:00
} ,
{
// 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
} ,
2017-10-05 13:08:08 +00:00
expectedError : "failed to recycle volume: recycler pod was deleted" ,
2016-09-08 10:57:57 +00:00
} ,
{
// Another recycler pod is already running
2017-10-05 13:08:08 +00:00
name : "RecyclerRunning" ,
existingPod : newPod ( "podOldRecycler" , v1 . PodRunning , "" ) ,
createPod : newPod ( "podNewRecycler" , v1 . PodFailed , "mock message" ) ,
eventSequence : [ ] watch . Event { } ,
expectedError : "old recycler pod found, will retry later" ,
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-21 18:19:48 +00:00
}
2016-02-12 08:46:59 +00:00
2017-02-21 18:19:48 +00:00
func TestMountOptionFromSpec ( t * testing . T ) {
scenarios := map [ string ] struct {
volume * Spec
expectedMountList [ ] string
systemOptions [ ] string
} {
"volume-with-mount-options" : {
volume : createVolumeSpecWithMountOption ( "good-mount-opts" , "ro,nfsvers=3" , v1 . PersistentVolumeSpec {
PersistentVolumeSource : v1 . PersistentVolumeSource {
NFS : & v1 . NFSVolumeSource { Server : "localhost" , Path : "/srv" , ReadOnly : false } ,
} ,
} ) ,
expectedMountList : [ ] string { "ro" , "nfsvers=3" } ,
systemOptions : nil ,
} ,
"volume-with-bad-mount-options" : {
volume : createVolumeSpecWithMountOption ( "good-mount-opts" , "" , v1 . PersistentVolumeSpec {
PersistentVolumeSource : v1 . PersistentVolumeSource {
NFS : & v1 . NFSVolumeSource { Server : "localhost" , Path : "/srv" , ReadOnly : false } ,
} ,
} ) ,
expectedMountList : [ ] string { } ,
systemOptions : nil ,
} ,
"vol-with-sys-opts" : {
volume : createVolumeSpecWithMountOption ( "good-mount-opts" , "ro,nfsvers=3" , v1 . PersistentVolumeSpec {
PersistentVolumeSource : v1 . PersistentVolumeSource {
NFS : & v1 . NFSVolumeSource { Server : "localhost" , Path : "/srv" , ReadOnly : false } ,
} ,
} ) ,
expectedMountList : [ ] string { "ro" , "nfsvers=3" , "fsid=100" , "hard" } ,
systemOptions : [ ] string { "fsid=100" , "hard" } ,
} ,
"vol-with-sys-opts-with-dup" : {
volume : createVolumeSpecWithMountOption ( "good-mount-opts" , "ro,nfsvers=3" , v1 . PersistentVolumeSpec {
PersistentVolumeSource : v1 . PersistentVolumeSource {
NFS : & v1 . NFSVolumeSource { Server : "localhost" , Path : "/srv" , ReadOnly : false } ,
} ,
} ) ,
expectedMountList : [ ] string { "ro" , "nfsvers=3" , "fsid=100" } ,
systemOptions : [ ] string { "fsid=100" , "ro" } ,
} ,
}
for name , scenario := range scenarios {
mountOptions := MountOptionFromSpec ( scenario . volume , scenario . systemOptions ... )
if ! reflect . DeepEqual ( slice . SortStrings ( mountOptions ) , slice . SortStrings ( scenario . expectedMountList ) ) {
t . Errorf ( "for %s expected mount options : %v got %v" , name , scenario . expectedMountList , mountOptions )
}
}
}
func createVolumeSpecWithMountOption ( name string , mountOptions string , spec v1 . PersistentVolumeSpec ) * Spec {
annotations := map [ string ] string {
2017-04-14 08:49:30 +00:00
v1 . MountOptionAnnotation : mountOptions ,
2017-02-21 18:19:48 +00:00
}
objMeta := metav1 . ObjectMeta {
Name : name ,
Annotations : annotations ,
}
pv := & v1 . PersistentVolume {
ObjectMeta : objMeta ,
Spec : spec ,
}
return & Spec { PersistentVolume : pv }
2016-02-12 08:46:59 +00:00
}
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 {
2017-08-31 00:49:50 +00:00
Zones sets . String
2017-02-03 05:57:20 +00:00
VolumeName string
Expected string
} {
// Test for PVC names that don't have a dash
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "henley" ,
Expected : "a" , // hash("henley") == 0
} ,
// Tests for PVC names that end in - number, but don't look like statefulset PVCs
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "henley-0" ,
Expected : "a" , // hash("henley") == 0
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "henley-1" ,
Expected : "b" , // hash("henley") + 1 == 1
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "henley-2" ,
Expected : "c" , // hash("henley") + 2 == 2
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "henley-3" ,
Expected : "a" , // hash("henley") + 3 == 3 === 0 mod 3
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "henley-4" ,
Expected : "b" , // hash("henley") + 4 == 4 === 1 mod 3
} ,
// Tests for PVC names that are edge cases
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "henley-" ,
Expected : "c" , // hash("henley-") = 2652299129 === 2 mod 3
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "henley-a" ,
Expected : "c" , // hash("henley-a") = 1459735322 === 2 mod 3
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium--1" ,
Expected : "c" , // hash("") + 1 == 2166136261 + 1 === 2 mod 3
} ,
// Tests for PVC names for simple StatefulSet cases
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-henley-1" ,
Expected : "b" , // hash("henley") + 1 == 1
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "loud-henley-1" ,
Expected : "b" , // hash("henley") + 1 == 1
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "quiet-henley-2" ,
Expected : "c" , // hash("henley") + 2 == 2
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-henley-2" ,
Expected : "c" , // hash("henley") + 2 == 2
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-henley-3" ,
Expected : "a" , // hash("henley") + 3 == 3 === 0 mod 3
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-henley-4" ,
Expected : "b" , // hash("henley") + 4 == 4 === 1 mod 3
} ,
// Tests for statefulsets (or claims) with dashes in the names
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-alpha-henley-2" ,
Expected : "c" , // hash("henley") + 2 == 2
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-beta-henley-3" ,
Expected : "a" , // hash("henley") + 3 == 3 === 0 mod 3
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-gamma-henley-4" ,
Expected : "b" , // hash("henley") + 4 == 4 === 1 mod 3
} ,
// Tests for statefulsets name ending in -
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-henley--2" ,
Expected : "a" , // hash("") + 2 == 0 mod 3
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-henley--3" ,
Expected : "b" , // hash("") + 3 == 1 mod 3
} ,
{
2017-08-31 00:49:50 +00:00
Zones : sets . NewString ( "a" , "b" , "c" ) ,
2017-02-03 05:57:20 +00:00
VolumeName : "medium-henley--4" ,
Expected : "c" , // hash("") + 4 == 2 mod 3
} ,
}
for _ , test := range tests {
2017-08-31 00:49:50 +00:00
actual := ChooseZoneForVolume ( test . Zones , test . VolumeName )
2017-02-03 05:57:20 +00:00
2017-08-31 00:49:50 +00:00
if actual != test . Expected {
2017-02-03 05:57:20 +00:00
t . Errorf ( "Test %v failed, expected zone %q, actual %q" , test , test . Expected , actual )
}
}
}
2016-11-10 11:32:35 +00:00
2017-08-31 00:49:50 +00:00
func TestChooseZonesForVolume ( 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 sets . String
VolumeName string
NumZones uint32
Expected sets . String
} {
// Test for PVC names that don't have a dash
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley" ,
NumZones : 1 ,
Expected : sets . NewString ( "a" /* hash("henley") == 0 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley" ,
NumZones : 2 ,
Expected : sets . NewString ( "a" /* hash("henley") == 0 */ , "b" ) ,
} ,
// Tests for PVC names that end in - number, but don't look like statefulset PVCs
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-0" ,
NumZones : 1 ,
Expected : sets . NewString ( "a" /* hash("henley") == 0 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-0" ,
NumZones : 2 ,
Expected : sets . NewString ( "a" /* hash("henley") == 0 */ , "b" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-1" ,
NumZones : 1 ,
Expected : sets . NewString ( "b" /* hash("henley") + 1 == 1 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-1" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley") + 1 + 1(startingIndex) == 2 */ , "a" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-2" ,
NumZones : 1 ,
Expected : sets . NewString ( "c" /* hash("henley") + 2 == 2 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-2" ,
NumZones : 2 ,
Expected : sets . NewString ( "b" /* hash("henley") + 2 + 2(startingIndex) == 4 */ , "c" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-3" ,
NumZones : 1 ,
Expected : sets . NewString ( "a" /* hash("henley") + 3 == 3 === 0 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-3" ,
NumZones : 2 ,
Expected : sets . NewString ( "a" /* hash("henley") + 3 + 3(startingIndex) == 6 */ , "b" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-4" ,
NumZones : 1 ,
Expected : sets . NewString ( "b" /* hash("henley") + 4 == 4 === 1 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-4" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley") + 4 + 4(startingIndex) == 8 */ , "a" ) ,
} ,
// Tests for PVC names that are edge cases
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-" ,
NumZones : 1 ,
Expected : sets . NewString ( "c" /* hash("henley-") = 2652299129 === 2 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley-") = 2652299129 === 2 mod 3 = 2 */ , "a" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-a" ,
NumZones : 1 ,
Expected : sets . NewString ( "c" /* hash("henley-a") = 1459735322 === 2 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "henley-a" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley-a") = 1459735322 === 2 mod 3 = 2 */ , "a" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium--1" ,
NumZones : 1 ,
Expected : sets . NewString ( "c" /* hash("") + 1 == 2166136261 + 1 === 2 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium--1" ,
NumZones : 2 ,
Expected : sets . NewString ( "a" /* hash("") + 1 + 1(startingIndex) == 2166136261 + 1 + 1 === 3 mod 3 = 0 */ , "b" ) ,
} ,
// Tests for PVC names for simple StatefulSet cases
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley-1" ,
NumZones : 1 ,
Expected : sets . NewString ( "b" /* hash("henley") + 1 == 1 */ ) ,
} ,
// Tests for PVC names for simple StatefulSet cases
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley-1" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley") + 1 + 1(startingIndex) == 2 */ , "a" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "loud-henley-1" ,
NumZones : 1 ,
Expected : sets . NewString ( "b" /* hash("henley") + 1 == 1 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "loud-henley-1" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley") + 1 + 1(startingIndex) == 2 */ , "a" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "quiet-henley-2" ,
NumZones : 1 ,
Expected : sets . NewString ( "c" /* hash("henley") + 2 == 2 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "quiet-henley-2" ,
NumZones : 2 ,
Expected : sets . NewString ( "b" /* hash("henley") + 2 + 2(startingIndex) == 4 */ , "c" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley-2" ,
NumZones : 1 ,
Expected : sets . NewString ( "c" /* hash("henley") + 2 == 2 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley-2" ,
NumZones : 2 ,
Expected : sets . NewString ( "b" /* hash("henley") + 2 + 2(startingIndex) == 4 */ , "c" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley-3" ,
NumZones : 1 ,
Expected : sets . NewString ( "a" /* hash("henley") + 3 == 3 === 0 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley-3" ,
NumZones : 2 ,
Expected : sets . NewString ( "a" /* hash("henley") + 3 + 3(startingIndex) == 6 === 6 mod 3 = 0 */ , "b" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley-4" ,
NumZones : 1 ,
Expected : sets . NewString ( "b" /* hash("henley") + 4 == 4 === 1 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley-4" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley") + 4 + 4(startingIndex) == 8 === 2 mod 3 */ , "a" ) ,
} ,
// Tests for statefulsets (or claims) with dashes in the names
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-alpha-henley-2" ,
NumZones : 1 ,
Expected : sets . NewString ( "c" /* hash("henley") + 2 == 2 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-alpha-henley-2" ,
NumZones : 2 ,
Expected : sets . NewString ( "b" /* hash("henley") + 2 + 2(startingIndex) == 4 */ , "c" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-beta-henley-3" ,
NumZones : 1 ,
Expected : sets . NewString ( "a" /* hash("henley") + 3 == 3 === 0 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-beta-henley-3" ,
NumZones : 2 ,
Expected : sets . NewString ( "a" /* hash("henley") + 3 + 3(startingIndex) == 6 === 0 mod 3 */ , "b" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-gamma-henley-4" ,
NumZones : 1 ,
Expected : sets . NewString ( "b" /* hash("henley") + 4 == 4 === 1 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-gamma-henley-4" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley") + 4 + 4(startingIndex) == 8 === 2 mod 3 */ , "a" ) ,
} ,
// Tests for statefulsets name ending in -
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley--2" ,
NumZones : 1 ,
Expected : sets . NewString ( "a" /* hash("") + 2 == 0 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley--2" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("") + 2 + 2(startingIndex) == 2 mod 3 */ , "a" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley--3" ,
NumZones : 1 ,
Expected : sets . NewString ( "b" /* hash("") + 3 == 1 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley--3" ,
NumZones : 2 ,
Expected : sets . NewString ( "b" /* hash("") + 3 + 3(startingIndex) == 1 mod 3 */ , "c" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley--4" ,
NumZones : 1 ,
Expected : sets . NewString ( "c" /* hash("") + 4 == 2 mod 3 */ ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley--4" ,
NumZones : 2 ,
Expected : sets . NewString ( "a" /* hash("") + 4 + 4(startingIndex) == 0 mod 3 */ , "b" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley--4" ,
NumZones : 3 ,
Expected : sets . NewString ( "c" /* hash("") + 4 == 2 mod 3 */ , "a" , "b" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" ) ,
VolumeName : "medium-henley--4" ,
NumZones : 4 ,
Expected : sets . NewString ( "c" /* hash("") + 4 + 9(startingIndex) == 2 mod 3 */ , "a" , "b" , "c" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" ) ,
VolumeName : "henley-0" ,
NumZones : 2 ,
Expected : sets . NewString ( "a" /* hash("henley") == 0 */ , "b" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" ) ,
VolumeName : "henley-1" ,
NumZones : 2 ,
Expected : sets . NewString ( "c" /* hash("henley") == 0 + 2 */ , "d" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" ) ,
VolumeName : "henley-2" ,
NumZones : 2 ,
Expected : sets . NewString ( "e" /* hash("henley") == 0 + 2 + 2(startingIndex) */ , "f" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" ) ,
VolumeName : "henley-3" ,
NumZones : 2 ,
Expected : sets . NewString ( "g" /* hash("henley") == 0 + 2 + 4(startingIndex) */ , "h" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" ) ,
VolumeName : "henley-0" ,
NumZones : 3 ,
Expected : sets . NewString ( "a" /* hash("henley") == 0 */ , "b" , "c" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" ) ,
VolumeName : "henley-1" ,
NumZones : 3 ,
Expected : sets . NewString ( "d" /* hash("henley") == 0 + 1 + 2(startingIndex) */ , "e" , "f" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" ) ,
VolumeName : "henley-2" ,
NumZones : 3 ,
Expected : sets . NewString ( "g" /* hash("henley") == 0 + 2 + 4(startingIndex) */ , "h" , "i" ) ,
} ,
{
Zones : sets . NewString ( "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" ) ,
VolumeName : "henley-3" ,
NumZones : 3 ,
Expected : sets . NewString ( "a" /* hash("henley") == 0 + 3 + 6(startingIndex) */ , "b" , "c" ) ,
} ,
}
for _ , test := range tests {
actual := ChooseZonesForVolume ( test . Zones , test . VolumeName , test . NumZones )
if ! actual . Equal ( test . Expected ) {
t . Errorf ( "Test %v failed, expected zone %#v, actual %#v" , test , test . Expected , actual )
}
}
}
2017-03-13 10:54:01 +00:00
func TestValidateZone ( t * testing . T ) {
functionUnderTest := "ValidateZone"
// First part: want an error
errCases := [ ] string { "" , " " }
for _ , errCase := range errCases {
if got := ValidateZone ( errCase ) ; got == nil {
t . Errorf ( "%v(%v) returned (%v), want (%v)" , functionUnderTest , errCase , got , "an error" )
}
}
// Second part: want no error
succCases := [ ] string { " us-east-1a " }
for _ , succCase := range succCases {
if got := ValidateZone ( succCase ) ; got != nil {
t . Errorf ( "%v(%v) returned (%v), want (%v)" , functionUnderTest , succCase , got , nil )
}
}
}
2017-12-07 06:24:20 +00:00
func TestGetWindowsPath ( t * testing . T ) {
tests := [ ] struct {
path string
expectedPath string
} {
{
path : ` /var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4/volumes/kubernetes.io~disk ` ,
expectedPath : ` c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk ` ,
} ,
{
path : ` \var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk ` ,
expectedPath : ` c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk ` ,
} ,
{
path : ` / ` ,
expectedPath : ` c:\ ` ,
} ,
{
path : ` ` ,
expectedPath : ` ` ,
} ,
}
for _ , test := range tests {
result := GetWindowsPath ( test . path )
if result != test . expectedPath {
t . Errorf ( "GetWindowsPath(%v) returned (%v), want (%v)" , test . path , result , test . expectedPath )
}
}
}