mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-10 08:20:23 +00:00
903 lines
29 KiB
Go
903 lines
29 KiB
Go
/*
|
|
Copyright 2015 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 volume
|
|
|
|
import (
|
|
"fmt"
|
|
"hash/fnv"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/apimachinery/pkg/watch"
|
|
api "k8s.io/kubernetes/pkg/apis/core"
|
|
"k8s.io/kubernetes/pkg/util/slice"
|
|
)
|
|
|
|
type testcase struct {
|
|
// Input of the test
|
|
name string
|
|
existingPod *v1.Pod
|
|
createPod *v1.Pod
|
|
// 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
|
|
|
|
// Expected output.
|
|
// expectedEvents is list of events that were sent to the volume that was
|
|
// recycled.
|
|
expectedEvents []mockEvent
|
|
expectedError string
|
|
}
|
|
|
|
func newPodEvent(eventtype watch.EventType, name string, phase v1.PodPhase, message string) watch.Event {
|
|
return watch.Event{
|
|
Type: eventtype,
|
|
Object: newPod(name, phase, message),
|
|
}
|
|
}
|
|
|
|
func newEvent(eventtype, message string) watch.Event {
|
|
return watch.Event{
|
|
Type: watch.Added,
|
|
Object: &v1.Event{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: metav1.NamespaceDefault,
|
|
},
|
|
Reason: "MockEvent",
|
|
Message: message,
|
|
Type: eventtype,
|
|
},
|
|
}
|
|
}
|
|
|
|
func newPod(name string, phase v1.PodPhase, message string) *v1.Pod {
|
|
return &v1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: metav1.NamespaceDefault,
|
|
Name: name,
|
|
},
|
|
Status: v1.PodStatus{
|
|
Phase: phase,
|
|
Message: message,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestRecyclerPod(t *testing.T) {
|
|
tests := []testcase{
|
|
{
|
|
// Test recycler success with some events
|
|
name: "RecyclerSuccess",
|
|
createPod: newPod("podRecyclerSuccess", v1.PodPending, ""),
|
|
eventSequence: []watch.Event{
|
|
// Pod gets Running and Succeeded
|
|
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, ""),
|
|
},
|
|
expectedEvents: []mockEvent{
|
|
{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"},
|
|
},
|
|
expectedError: "",
|
|
},
|
|
{
|
|
// Test recycler failure with some events
|
|
name: "RecyclerFailure",
|
|
createPod: newPod("podRecyclerFailure", v1.PodPending, ""),
|
|
eventSequence: []watch.Event{
|
|
// Pod gets Running and Succeeded
|
|
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"),
|
|
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"),
|
|
newPodEvent(watch.Modified, "podRecyclerFailure", v1.PodRunning, ""),
|
|
newPodEvent(watch.Modified, "podRecyclerFailure", v1.PodFailed, "Pod was active on the node longer than specified deadline"),
|
|
},
|
|
expectedEvents: []mockEvent{
|
|
{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"},
|
|
{v1.EventTypeWarning, "Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod \"default\"/\"recycler-for-podRecyclerFailure\". list of unattached/unmounted"},
|
|
},
|
|
expectedError: "failed to recycle volume: Pod was active on the node longer than specified deadline",
|
|
},
|
|
{
|
|
// Recycler pod gets deleted
|
|
name: "RecyclerDeleted",
|
|
createPod: newPod("podRecyclerDeleted", v1.PodPending, ""),
|
|
eventSequence: []watch.Event{
|
|
// Pod gets Running and Succeeded
|
|
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, ""),
|
|
},
|
|
expectedEvents: []mockEvent{
|
|
{v1.EventTypeNormal, "Successfully assigned recycler-for-podRecyclerDeleted to 127.0.0.1"},
|
|
},
|
|
expectedError: "failed to recycle volume: recycler pod was deleted",
|
|
},
|
|
{
|
|
// Another recycler pod is already running
|
|
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",
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
type mockRecyclerClient struct {
|
|
pod *v1.Pod
|
|
deletedCalled bool
|
|
receivedEvents []mockEvent
|
|
events []watch.Event
|
|
}
|
|
|
|
type mockEvent struct {
|
|
eventtype, message string
|
|
}
|
|
|
|
func (c *mockRecyclerClient) CreatePod(pod *v1.Pod) (*v1.Pod, error) {
|
|
if c.pod == nil {
|
|
c.pod = pod
|
|
return c.pod, nil
|
|
}
|
|
// Simulate "already exists" error
|
|
return nil, errors.NewAlreadyExists(api.Resource("pods"), pod.Name)
|
|
}
|
|
|
|
func (c *mockRecyclerClient) GetPod(name, namespace string) (*v1.Pod, error) {
|
|
if c.pod != nil {
|
|
return c.pod, nil
|
|
} else {
|
|
return nil, fmt.Errorf("pod does not exist")
|
|
}
|
|
}
|
|
|
|
func (c *mockRecyclerClient) DeletePod(name, namespace string) error {
|
|
c.deletedCalled = true
|
|
return nil
|
|
}
|
|
|
|
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})
|
|
}
|
|
|
|
func TestCalculateTimeoutForVolume(t *testing.T) {
|
|
pv := &v1.PersistentVolume{
|
|
Spec: v1.PersistentVolumeSpec{
|
|
Capacity: v1.ResourceList{
|
|
v1.ResourceName(v1.ResourceStorage): resource.MustParse("500M"),
|
|
},
|
|
},
|
|
}
|
|
|
|
timeout := CalculateTimeoutForVolume(50, 30, pv)
|
|
if timeout != 50 {
|
|
t.Errorf("Expected 50 for timeout but got %v", timeout)
|
|
}
|
|
|
|
pv.Spec.Capacity[v1.ResourceStorage] = resource.MustParse("2Gi")
|
|
timeout = CalculateTimeoutForVolume(50, 30, pv)
|
|
if timeout != 60 {
|
|
t.Errorf("Expected 60 for timeout but got %v", timeout)
|
|
}
|
|
|
|
pv.Spec.Capacity[v1.ResourceStorage] = resource.MustParse("150Gi")
|
|
timeout = CalculateTimeoutForVolume(50, 30, pv)
|
|
if timeout != 4500 {
|
|
t.Errorf("Expected 4500 for timeout but got %v", timeout)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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{
|
|
v1.MountOptionAnnotation: mountOptions,
|
|
}
|
|
objMeta := metav1.ObjectMeta{
|
|
Name: name,
|
|
Annotations: annotations,
|
|
}
|
|
|
|
pv := &v1.PersistentVolume{
|
|
ObjectMeta: objMeta,
|
|
Spec: spec,
|
|
}
|
|
return &Spec{PersistentVolume: pv}
|
|
}
|
|
|
|
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 sets.String
|
|
VolumeName string
|
|
Expected string
|
|
}{
|
|
// Test for PVC names that don't have a dash
|
|
{
|
|
Zones: sets.NewString("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: sets.NewString("a", "b", "c"),
|
|
VolumeName: "henley-0",
|
|
Expected: "a", // hash("henley") == 0
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "henley-1",
|
|
Expected: "b", // hash("henley") + 1 == 1
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "henley-2",
|
|
Expected: "c", // hash("henley") + 2 == 2
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "henley-3",
|
|
Expected: "a", // hash("henley") + 3 == 3 === 0 mod 3
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "henley-4",
|
|
Expected: "b", // hash("henley") + 4 == 4 === 1 mod 3
|
|
},
|
|
// Tests for PVC names that are edge cases
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "henley-",
|
|
Expected: "c", // hash("henley-") = 2652299129 === 2 mod 3
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "henley-a",
|
|
Expected: "c", // hash("henley-a") = 1459735322 === 2 mod 3
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium--1",
|
|
Expected: "c", // hash("") + 1 == 2166136261 + 1 === 2 mod 3
|
|
},
|
|
// Tests for PVC names for simple StatefulSet cases
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-henley-1",
|
|
Expected: "b", // hash("henley") + 1 == 1
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "loud-henley-1",
|
|
Expected: "b", // hash("henley") + 1 == 1
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "quiet-henley-2",
|
|
Expected: "c", // hash("henley") + 2 == 2
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-henley-2",
|
|
Expected: "c", // hash("henley") + 2 == 2
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-henley-3",
|
|
Expected: "a", // hash("henley") + 3 == 3 === 0 mod 3
|
|
},
|
|
{
|
|
Zones: sets.NewString("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: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-alpha-henley-2",
|
|
Expected: "c", // hash("henley") + 2 == 2
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-beta-henley-3",
|
|
Expected: "a", // hash("henley") + 3 == 3 === 0 mod 3
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-gamma-henley-4",
|
|
Expected: "b", // hash("henley") + 4 == 4 === 1 mod 3
|
|
},
|
|
// Tests for statefulsets name ending in -
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-henley--2",
|
|
Expected: "a", // hash("") + 2 == 0 mod 3
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-henley--3",
|
|
Expected: "b", // hash("") + 3 == 1 mod 3
|
|
},
|
|
{
|
|
Zones: sets.NewString("a", "b", "c"),
|
|
VolumeName: "medium-henley--4",
|
|
Expected: "c", // hash("") + 4 == 2 mod 3
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
actual := ChooseZoneForVolume(test.Zones, test.VolumeName)
|
|
|
|
if actual != test.Expected {
|
|
t.Errorf("Test %v failed, expected zone %q, actual %q", test, test.Expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|