Switch to github.com/golang/dep for vendoring
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
parent
d6ab91be27
commit
8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions
98
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/BUILD
generated
vendored
Normal file
98
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"index.go",
|
||||
"pv_controller.go",
|
||||
"pv_controller_base.go",
|
||||
"volume_host.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/storage/v1beta1:go_default_library",
|
||||
"//pkg/apis/storage/v1beta1/util:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
|
||||
"//pkg/client/record:go_default_library",
|
||||
"//pkg/cloudprovider:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/util/goroutinemap:go_default_library",
|
||||
"//pkg/util/io:go_default_library",
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/util/workqueue:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/labels",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"binder_test.go",
|
||||
"delete_test.go",
|
||||
"framework_test.go",
|
||||
"index_test.go",
|
||||
"provision_test.go",
|
||||
"pv_controller_test.go",
|
||||
"recycle_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/resource:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/storage/v1beta1:go_default_library",
|
||||
"//pkg/apis/storage/v1beta1/util:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset/fake:go_default_library",
|
||||
"//pkg/client/record:go_default_library",
|
||||
"//pkg/client/testing/cache:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/diff",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/controller/volume/persistentvolume/options:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
4
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/OWNERS
generated
vendored
Normal file
4
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/OWNERS
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
approvers:
|
||||
- jsafrane
|
||||
- saad-ali
|
||||
- thockin
|
555
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/binder_test.go
generated
vendored
Normal file
555
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/binder_test.go
generated
vendored
Normal file
|
@ -0,0 +1,555 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package persistentvolume
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
storage "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
|
||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/v1beta1/util"
|
||||
)
|
||||
|
||||
// Test single call to syncClaim and syncVolume methods.
|
||||
// 1. Fill in the controller with initial data
|
||||
// 2. Call the tested function (syncClaim/syncVolume) via
|
||||
// controllerTest.testCall *once*.
|
||||
// 3. Compare resulting volumes and claims with expected volumes and claims.
|
||||
func TestSync(t *testing.T) {
|
||||
labels := map[string]string{
|
||||
"foo": "true",
|
||||
"bar": "false",
|
||||
}
|
||||
|
||||
tests := []controllerTest{
|
||||
// [Unit test set 1] User did not care which PV they get.
|
||||
// Test the matching with no claim.Spec.VolumeName and with various
|
||||
// volumes.
|
||||
{
|
||||
// syncClaim binds to a matching unbound volume.
|
||||
"1-1 - successful bind",
|
||||
newVolumeArray("volume1-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume1-1", "1Gi", "uid1-1", "claim1-1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim1-1", "uid1-1", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim1-1", "uid1-1", "1Gi", "volume1-1", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim does not do anything when there is no matching volume.
|
||||
"1-2 - noop",
|
||||
newVolumeArray("volume1-2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume1-2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim1-2", "uid1-2", "10Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim1-2", "uid1-2", "10Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim resets claim.Status to Pending when there is no
|
||||
// matching volume.
|
||||
"1-3 - reset to Pending",
|
||||
newVolumeArray("volume1-3", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume1-3", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim1-3", "uid1-3", "10Gi", "", v1.ClaimBound),
|
||||
newClaimArray("claim1-3", "uid1-3", "10Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim binds claims to the smallest matching volume
|
||||
"1-4 - smallest volume",
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume1-4_1", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume1-4_2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
},
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume1-4_1", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume1-4_2", "1Gi", "uid1-4", "claim1-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
},
|
||||
newClaimArray("claim1-4", "uid1-4", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim1-4", "uid1-4", "1Gi", "volume1-4_2", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim binds a claim only to volume that points to it (by
|
||||
// name), even though a smaller one is available.
|
||||
"1-5 - prebound volume by name - success",
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume1-5_1", "10Gi", "", "claim1-5", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume1-5_2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
},
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume1-5_1", "10Gi", "uid1-5", "claim1-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume1-5_2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
},
|
||||
newClaimArray("claim1-5", "uid1-5", "1Gi", "", v1.ClaimPending),
|
||||
withExpectedCapacity("10Gi", newClaimArray("claim1-5", "uid1-5", "1Gi", "volume1-5_1", v1.ClaimBound, annBoundByController, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim binds a claim only to volume that points to it (by
|
||||
// UID), even though a smaller one is available.
|
||||
"1-6 - prebound volume by UID - success",
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume1-6_1", "10Gi", "uid1-6", "claim1-6", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume1-6_2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
},
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume1-6_1", "10Gi", "uid1-6", "claim1-6", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume1-6_2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
},
|
||||
newClaimArray("claim1-6", "uid1-6", "1Gi", "", v1.ClaimPending),
|
||||
withExpectedCapacity("10Gi", newClaimArray("claim1-6", "uid1-6", "1Gi", "volume1-6_1", v1.ClaimBound, annBoundByController, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim does not bind claim to a volume prebound to a claim with
|
||||
// same name and different UID
|
||||
"1-7 - prebound volume to different claim",
|
||||
newVolumeArray("volume1-7", "10Gi", "uid1-777", "claim1-7", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume1-7", "10Gi", "uid1-777", "claim1-7", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim1-7", "uid1-7", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim1-7", "uid1-7", "1Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim completes binding - simulates controller crash after
|
||||
// PV.ClaimRef is saved
|
||||
"1-8 - complete bind after crash - PV bound",
|
||||
newVolumeArray("volume1-8", "1Gi", "uid1-8", "claim1-8", v1.VolumePending, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newVolumeArray("volume1-8", "1Gi", "uid1-8", "claim1-8", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim1-8", "uid1-8", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim1-8", "uid1-8", "1Gi", "volume1-8", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim completes binding - simulates controller crash after
|
||||
// PV.Status is saved
|
||||
"1-9 - complete bind after crash - PV status saved",
|
||||
newVolumeArray("volume1-9", "1Gi", "uid1-9", "claim1-9", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newVolumeArray("volume1-9", "1Gi", "uid1-9", "claim1-9", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim1-9", "uid1-9", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim1-9", "uid1-9", "1Gi", "volume1-9", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim completes binding - simulates controller crash after
|
||||
// PVC.VolumeName is saved
|
||||
"1-10 - complete bind after crash - PVC bound",
|
||||
newVolumeArray("volume1-10", "1Gi", "uid1-10", "claim1-10", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newVolumeArray("volume1-10", "1Gi", "uid1-10", "claim1-10", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim1-10", "uid1-10", "1Gi", "volume1-10", v1.ClaimPending, annBoundByController, annBindCompleted),
|
||||
newClaimArray("claim1-10", "uid1-10", "1Gi", "volume1-10", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim binds a claim only when the label selector matches the volume
|
||||
"1-11 - bind when selector matches",
|
||||
withLabels(labels, newVolumeArray("volume1-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain)),
|
||||
withLabels(labels, newVolumeArray("volume1-1", "1Gi", "uid1-1", "claim1-1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController)),
|
||||
withLabelSelector(labels, newClaimArray("claim1-1", "uid1-1", "1Gi", "", v1.ClaimPending)),
|
||||
withLabelSelector(labels, newClaimArray("claim1-1", "uid1-1", "1Gi", "volume1-1", v1.ClaimBound, annBoundByController, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim does not bind a claim when the label selector doesn't match
|
||||
"1-12 - do not bind when selector does not match",
|
||||
newVolumeArray("volume1-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume1-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
withLabelSelector(labels, newClaimArray("claim1-1", "uid1-1", "1Gi", "", v1.ClaimPending)),
|
||||
withLabelSelector(labels, newClaimArray("claim1-1", "uid1-1", "1Gi", "", v1.ClaimPending)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
|
||||
// [Unit test set 2] User asked for a specific PV.
|
||||
// Test the binding when pv.ClaimRef is already set by controller or
|
||||
// by user.
|
||||
{
|
||||
// syncClaim with claim pre-bound to a PV that does not exist
|
||||
"2-1 - claim prebound to non-existing volume - noop",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim2-1", "uid2-1", "10Gi", "volume2-1", v1.ClaimPending),
|
||||
newClaimArray("claim2-1", "uid2-1", "10Gi", "volume2-1", v1.ClaimPending),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim pre-bound to a PV that does not exist.
|
||||
// Check that the claim status is reset to Pending
|
||||
"2-2 - claim prebound to non-existing volume - reset status",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim2-2", "uid2-2", "10Gi", "volume2-2", v1.ClaimBound),
|
||||
newClaimArray("claim2-2", "uid2-2", "10Gi", "volume2-2", v1.ClaimPending),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim pre-bound to a PV that exists and is
|
||||
// unbound. Check it gets bound and no annBoundByController is set.
|
||||
"2-3 - claim prebound to unbound volume",
|
||||
newVolumeArray("volume2-3", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume2-3", "1Gi", "uid2-3", "claim2-3", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim2-3", "uid2-3", "1Gi", "volume2-3", v1.ClaimPending),
|
||||
newClaimArray("claim2-3", "uid2-3", "1Gi", "volume2-3", v1.ClaimBound, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// claim with claim pre-bound to a PV that is pre-bound to the claim
|
||||
// by name. Check it gets bound and no annBoundByController is set.
|
||||
"2-4 - claim prebound to prebound volume by name",
|
||||
newVolumeArray("volume2-4", "1Gi", "", "claim2-4", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume2-4", "1Gi", "uid2-4", "claim2-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim2-4", "uid2-4", "1Gi", "volume2-4", v1.ClaimPending),
|
||||
newClaimArray("claim2-4", "uid2-4", "1Gi", "volume2-4", v1.ClaimBound, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim pre-bound to a PV that is pre-bound to the
|
||||
// claim by UID. Check it gets bound and no annBoundByController is
|
||||
// set.
|
||||
"2-5 - claim prebound to prebound volume by UID",
|
||||
newVolumeArray("volume2-5", "1Gi", "uid2-5", "claim2-5", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume2-5", "1Gi", "uid2-5", "claim2-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim2-5", "uid2-5", "1Gi", "volume2-5", v1.ClaimPending),
|
||||
newClaimArray("claim2-5", "uid2-5", "1Gi", "volume2-5", v1.ClaimBound, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim pre-bound to a PV that is bound to different
|
||||
// claim. Check it's reset to Pending.
|
||||
"2-6 - claim prebound to already bound volume",
|
||||
newVolumeArray("volume2-6", "1Gi", "uid2-6_1", "claim2-6_1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume2-6", "1Gi", "uid2-6_1", "claim2-6_1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim2-6", "uid2-6", "1Gi", "volume2-6", v1.ClaimBound),
|
||||
newClaimArray("claim2-6", "uid2-6", "1Gi", "volume2-6", v1.ClaimPending),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim bound by controller to a PV that is bound to
|
||||
// different claim. Check it throws an error.
|
||||
"2-7 - claim bound by controller to already bound volume",
|
||||
newVolumeArray("volume2-7", "1Gi", "uid2-7_1", "claim2-7_1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume2-7", "1Gi", "uid2-7_1", "claim2-7_1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim2-7", "uid2-7", "1Gi", "volume2-7", v1.ClaimBound, annBoundByController),
|
||||
newClaimArray("claim2-7", "uid2-7", "1Gi", "volume2-7", v1.ClaimBound, annBoundByController),
|
||||
noevents, noerrors, testSyncClaimError,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim pre-bound to a PV that exists and is
|
||||
// unbound, but does not match the selector. Check it gets bound
|
||||
// and no annBoundByController is set.
|
||||
"2-8 - claim prebound to unbound volume that does not match the selector",
|
||||
newVolumeArray("volume2-3", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume2-3", "1Gi", "uid2-3", "claim2-3", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
withLabelSelector(labels, newClaimArray("claim2-3", "uid2-3", "1Gi", "volume2-3", v1.ClaimPending)),
|
||||
withLabelSelector(labels, newClaimArray("claim2-3", "uid2-3", "1Gi", "volume2-3", v1.ClaimBound, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
|
||||
// [Unit test set 3] Syncing bound claim
|
||||
{
|
||||
// syncClaim with claim bound and its claim.Spec.VolumeName is
|
||||
// removed. Check it's marked as Lost.
|
||||
"3-1 - bound claim with missing VolumeName",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim3-1", "uid3-1", "10Gi", "", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
newClaimArray("claim3-1", "uid3-1", "10Gi", "", v1.ClaimLost, annBoundByController, annBindCompleted),
|
||||
[]string{"Warning ClaimLost"}, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim bound to non-existing volume. Check it's
|
||||
// marked as Lost.
|
||||
"3-2 - bound claim with missing volume",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim3-2", "uid3-2", "10Gi", "volume3-2", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
newClaimArray("claim3-2", "uid3-2", "10Gi", "volume3-2", v1.ClaimLost, annBoundByController, annBindCompleted),
|
||||
[]string{"Warning ClaimLost"}, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim bound to unbound volume. Check it's bound.
|
||||
// Also check that Pending phase is set to Bound
|
||||
"3-3 - bound claim with unbound volume",
|
||||
newVolumeArray("volume3-3", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume3-3", "10Gi", "uid3-3", "claim3-3", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim3-3", "uid3-3", "10Gi", "volume3-3", v1.ClaimPending, annBoundByController, annBindCompleted),
|
||||
newClaimArray("claim3-3", "uid3-3", "10Gi", "volume3-3", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim bound to volume with missing (or different)
|
||||
// volume.Spec.ClaimRef.UID. Check that the claim is marked as lost.
|
||||
"3-4 - bound claim with prebound volume",
|
||||
newVolumeArray("volume3-4", "10Gi", "claim3-4-x", "claim3-4", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume3-4", "10Gi", "claim3-4-x", "claim3-4", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim3-4", "uid3-4", "10Gi", "volume3-4", v1.ClaimPending, annBoundByController, annBindCompleted),
|
||||
newClaimArray("claim3-4", "uid3-4", "10Gi", "volume3-4", v1.ClaimLost, annBoundByController, annBindCompleted),
|
||||
[]string{"Warning ClaimMisbound"}, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim bound to bound volume. Check that the
|
||||
// controller does not do anything. Also check that Pending phase is
|
||||
// set to Bound
|
||||
"3-5 - bound claim with bound volume",
|
||||
newVolumeArray("volume3-5", "10Gi", "uid3-5", "claim3-5", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume3-5", "10Gi", "uid3-5", "claim3-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim3-5", "uid3-5", "10Gi", "volume3-5", v1.ClaimPending, annBindCompleted),
|
||||
newClaimArray("claim3-5", "uid3-5", "10Gi", "volume3-5", v1.ClaimBound, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim bound to a volume that is bound to different
|
||||
// claim. Check that the claim is marked as lost.
|
||||
// TODO: test that an event is emitted
|
||||
"3-6 - bound claim with bound volume",
|
||||
newVolumeArray("volume3-6", "10Gi", "uid3-6-x", "claim3-6-x", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume3-6", "10Gi", "uid3-6-x", "claim3-6-x", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim3-6", "uid3-6", "10Gi", "volume3-6", v1.ClaimPending, annBindCompleted),
|
||||
newClaimArray("claim3-6", "uid3-6", "10Gi", "volume3-6", v1.ClaimLost, annBindCompleted),
|
||||
[]string{"Warning ClaimMisbound"}, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncClaim with claim bound to unbound volume. Check it's bound
|
||||
// even if the claim's selector doesn't match the volume. Also
|
||||
// check that Pending phase is set to Bound
|
||||
"3-7 - bound claim with unbound volume where selector doesn't match",
|
||||
newVolumeArray("volume3-3", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume3-3", "10Gi", "uid3-3", "claim3-3", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
withLabelSelector(labels, newClaimArray("claim3-3", "uid3-3", "10Gi", "volume3-3", v1.ClaimPending, annBoundByController, annBindCompleted)),
|
||||
withLabelSelector(labels, newClaimArray("claim3-3", "uid3-3", "10Gi", "volume3-3", v1.ClaimBound, annBoundByController, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
// [Unit test set 4] All syncVolume tests.
|
||||
{
|
||||
// syncVolume with pending volume. Check it's marked as Available.
|
||||
"4-1 - pending volume",
|
||||
newVolumeArray("volume4-1", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume4-1", "10Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain),
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// syncVolume with prebound pending volume. Check it's marked as
|
||||
// Available.
|
||||
"4-2 - pending prebound volume",
|
||||
newVolumeArray("volume4-2", "10Gi", "", "claim4-2", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume4-2", "10Gi", "", "claim4-2", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain),
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// syncVolume with volume bound to missing claim.
|
||||
// Check the volume gets Released
|
||||
"4-3 - bound volume with missing claim",
|
||||
newVolumeArray("volume4-3", "10Gi", "uid4-3", "claim4-3", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume4-3", "10Gi", "uid4-3", "claim4-3", v1.VolumeReleased, v1.PersistentVolumeReclaimRetain),
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// syncVolume with volume bound to claim with different UID.
|
||||
// Check the volume gets Released.
|
||||
"4-4 - volume bound to claim with different UID",
|
||||
newVolumeArray("volume4-4", "10Gi", "uid4-4", "claim4-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume4-4", "10Gi", "uid4-4", "claim4-4", v1.VolumeReleased, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim4-4", "uid4-4-x", "10Gi", "volume4-4", v1.ClaimBound, annBindCompleted),
|
||||
newClaimArray("claim4-4", "uid4-4-x", "10Gi", "volume4-4", v1.ClaimBound, annBindCompleted),
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// syncVolume with volume bound by controller to unbound claim.
|
||||
// Check syncVolume does not do anything.
|
||||
"4-5 - volume bound by controller to unbound claim",
|
||||
newVolumeArray("volume4-5", "10Gi", "uid4-5", "claim4-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newVolumeArray("volume4-5", "10Gi", "uid4-5", "claim4-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim4-5", "uid4-5", "10Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim4-5", "uid4-5", "10Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// syncVolume with volume bound by user to unbound claim.
|
||||
// Check syncVolume does not do anything.
|
||||
"4-5 - volume bound by user to bound claim",
|
||||
newVolumeArray("volume4-5", "10Gi", "uid4-5", "claim4-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume4-5", "10Gi", "uid4-5", "claim4-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim4-5", "uid4-5", "10Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim4-5", "uid4-5", "10Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// syncVolume with volume bound to bound claim.
|
||||
// Check that the volume is marked as Bound.
|
||||
"4-6 - volume bound by to bound claim",
|
||||
newVolumeArray("volume4-6", "10Gi", "uid4-6", "claim4-6", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume4-6", "10Gi", "uid4-6", "claim4-6", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim4-6", "uid4-6", "10Gi", "volume4-6", v1.ClaimBound),
|
||||
newClaimArray("claim4-6", "uid4-6", "10Gi", "volume4-6", v1.ClaimBound),
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// syncVolume with volume bound by controller to claim bound to
|
||||
// another volume. Check that the volume is rolled back.
|
||||
"4-7 - volume bound by controller to claim bound somewhere else",
|
||||
newVolumeArray("volume4-7", "10Gi", "uid4-7", "claim4-7", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newVolumeArray("volume4-7", "10Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim4-7", "uid4-7", "10Gi", "volume4-7-x", v1.ClaimBound),
|
||||
newClaimArray("claim4-7", "uid4-7", "10Gi", "volume4-7-x", v1.ClaimBound),
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// syncVolume with volume bound by user to claim bound to
|
||||
// another volume. Check that the volume is marked as Available
|
||||
// and its UID is reset.
|
||||
"4-8 - volume bound by user to claim bound somewhere else",
|
||||
newVolumeArray("volume4-8", "10Gi", "uid4-8", "claim4-8", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume4-8", "10Gi", "", "claim4-8", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain),
|
||||
newClaimArray("claim4-8", "uid4-8", "10Gi", "volume4-8-x", v1.ClaimBound),
|
||||
newClaimArray("claim4-8", "uid4-8", "10Gi", "volume4-8-x", v1.ClaimBound),
|
||||
noevents, noerrors, testSyncVolume,
|
||||
},
|
||||
|
||||
// PVC with class
|
||||
{
|
||||
// syncVolume binds a claim to requested class even if there is a
|
||||
// smaller PV available
|
||||
"13-1 - binding to class",
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume13-1-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume13-1-2", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation),
|
||||
},
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume13-1-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume13-1-2", "10Gi", "uid13-1", "claim13-1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController, storageutil.StorageClassAnnotation),
|
||||
},
|
||||
newClaimArray("claim13-1", "uid13-1", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
withExpectedCapacity("10Gi", newClaimArray("claim13-1", "uid13-1", "1Gi", "volume13-1-2", v1.ClaimBound, annBoundByController, storageutil.StorageClassAnnotation, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncVolume binds a claim without a class even if there is a
|
||||
// smaller PV with a class available
|
||||
"13-2 - binding without a class",
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume13-2-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation),
|
||||
newVolume("volume13-2-2", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
},
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume13-2-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation),
|
||||
newVolume("volume13-2-2", "10Gi", "uid13-2", "claim13-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
},
|
||||
newClaimArray("claim13-2", "uid13-2", "1Gi", "", v1.ClaimPending),
|
||||
withExpectedCapacity("10Gi", newClaimArray("claim13-2", "uid13-2", "1Gi", "volume13-2-2", v1.ClaimBound, annBoundByController, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncVolume binds a claim with given class even if there is a
|
||||
// smaller PV with different class available
|
||||
"13-3 - binding to specific a class",
|
||||
volumeWithClass("silver", []*v1.PersistentVolume{
|
||||
newVolume("volume13-3-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume13-3-2", "10Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation),
|
||||
}),
|
||||
volumeWithClass("silver", []*v1.PersistentVolume{
|
||||
newVolume("volume13-3-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("volume13-3-2", "10Gi", "uid13-3", "claim13-3", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController, storageutil.StorageClassAnnotation),
|
||||
}),
|
||||
newClaimArray("claim13-3", "uid13-3", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
withExpectedCapacity("10Gi", newClaimArray("claim13-3", "uid13-3", "1Gi", "volume13-3-2", v1.ClaimBound, annBoundByController, annBindCompleted, storageutil.StorageClassAnnotation)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncVolume binds claim requesting class "" to claim to PV with
|
||||
// class=""
|
||||
"13-4 - empty class",
|
||||
volumeWithClass("", newVolumeArray("volume13-4", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain)),
|
||||
volumeWithClass("", newVolumeArray("volume13-4", "1Gi", "uid13-4", "claim13-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController)),
|
||||
claimWithClass("", newClaimArray("claim13-4", "uid13-4", "1Gi", "", v1.ClaimPending)),
|
||||
claimWithClass("", newClaimArray("claim13-4", "uid13-4", "1Gi", "volume13-4", v1.ClaimBound, annBoundByController, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncVolume binds claim requesting class nil to claim to PV with
|
||||
// class = ""
|
||||
"13-5 - nil class",
|
||||
volumeWithClass("", newVolumeArray("volume13-5", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain)),
|
||||
volumeWithClass("", newVolumeArray("volume13-5", "1Gi", "uid13-5", "claim13-5", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController)),
|
||||
newClaimArray("claim13-5", "uid13-5", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim13-5", "uid13-5", "1Gi", "volume13-5", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// syncVolume binds claim requesting class "" to claim to PV with
|
||||
// class=nil
|
||||
"13-6 - nil class in PV, '' class in claim",
|
||||
newVolumeArray("volume13-6", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume13-6", "1Gi", "uid13-6", "claim13-6", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
claimWithClass("", newClaimArray("claim13-6", "uid13-6", "1Gi", "", v1.ClaimPending)),
|
||||
claimWithClass("", newClaimArray("claim13-6", "uid13-6", "1Gi", "volume13-6", v1.ClaimBound, annBoundByController, annBindCompleted)),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
}
|
||||
runSyncTests(t, tests, []*storage.StorageClass{})
|
||||
}
|
||||
|
||||
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
||||
// volume/claims. The test follows this pattern:
|
||||
// 0. Load the controller with initial data.
|
||||
// 1. Call controllerTest.testCall() once as in TestSync()
|
||||
// 2. For all volumes/claims changed by previous syncVolume/syncClaim calls,
|
||||
// call appropriate syncVolume/syncClaim (simulating "volume/claim changed"
|
||||
// events). Go to 2. if these calls change anything.
|
||||
// 3. When all changes are processed and no new changes were made, call
|
||||
// syncVolume/syncClaim on all volumes/claims (simulating "periodic sync").
|
||||
// 4. If some changes were done by step 3., go to 2. (simulation of
|
||||
// "volume/claim updated" events, eventually performing step 3. again)
|
||||
// 5. When 3. does not do any changes, finish the tests and compare final set
|
||||
// of volumes/claims with expected claims/volumes and report differences.
|
||||
// Some limit of calls in enforced to prevent endless loops.
|
||||
func TestMultiSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
// Test simple binding
|
||||
{
|
||||
// syncClaim binds to a matching unbound volume.
|
||||
"10-1 - successful bind",
|
||||
newVolumeArray("volume10-1", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume10-1", "1Gi", "uid10-1", "claim10-1", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim10-1", "uid10-1", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim10-1", "uid10-1", "1Gi", "volume10-1", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
{
|
||||
// Two controllers bound two PVs to single claim. Test one of them
|
||||
// wins and the second rolls back.
|
||||
"10-2 - bind PV race",
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume10-2-1", "1Gi", "uid10-2", "claim10-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newVolume("volume10-2-2", "1Gi", "uid10-2", "claim10-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
},
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume10-2-1", "1Gi", "uid10-2", "claim10-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newVolume("volume10-2-2", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain),
|
||||
},
|
||||
newClaimArray("claim10-2", "uid10-2", "1Gi", "volume10-2-1", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
newClaimArray("claim10-2", "uid10-2", "1Gi", "volume10-2-1", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors, testSyncClaim,
|
||||
},
|
||||
}
|
||||
|
||||
runMultisyncTests(t, tests, []*storage.StorageClass{}, "")
|
||||
}
|
228
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/delete_test.go
generated
vendored
Normal file
228
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/delete_test.go
generated
vendored
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package persistentvolume
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
storage "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
|
||||
)
|
||||
|
||||
// Test single call to syncVolume, expecting recycling to happen.
|
||||
// 1. Fill in the controller with initial data
|
||||
// 2. Call the syncVolume *once*.
|
||||
// 3. Compare resulting volumes with expected volumes.
|
||||
func TestDeleteSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
{
|
||||
// delete volume bound by controller
|
||||
"8-1 - successful delete",
|
||||
newVolumeArray("volume8-1", "1Gi", "uid8-1", "claim8-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController),
|
||||
novolumes,
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
// Inject deleter into the controller and call syncVolume. The
|
||||
// deleter simulates one delete() call that succeeds.
|
||||
wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// delete volume bound by user
|
||||
"8-2 - successful delete with prebound volume",
|
||||
newVolumeArray("volume8-2", "1Gi", "uid8-2", "claim8-2", v1.VolumeBound, v1.PersistentVolumeReclaimDelete),
|
||||
novolumes,
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
// Inject deleter into the controller and call syncVolume. The
|
||||
// deleter simulates one delete() call that succeeds.
|
||||
wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// delete failure - plugin not found
|
||||
"8-3 - plugin not found",
|
||||
newVolumeArray("volume8-3", "1Gi", "uid8-3", "claim8-3", v1.VolumeBound, v1.PersistentVolumeReclaimDelete),
|
||||
withMessage("Error getting deleter volume plugin for volume \"volume8-3\": no volume plugin matched", newVolumeArray("volume8-3", "1Gi", "uid8-3", "claim8-3", v1.VolumeFailed, v1.PersistentVolumeReclaimDelete)),
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeFailedDelete"}, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// delete failure - newDeleter returns error
|
||||
"8-4 - newDeleter returns error",
|
||||
newVolumeArray("volume8-4", "1Gi", "uid8-4", "claim8-4", v1.VolumeBound, v1.PersistentVolumeReclaimDelete),
|
||||
withMessage("Failed to create deleter for volume \"volume8-4\": Mock plugin error: no deleteCalls configured", newVolumeArray("volume8-4", "1Gi", "uid8-4", "claim8-4", v1.VolumeFailed, v1.PersistentVolumeReclaimDelete)),
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
||||
wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// delete failure - delete() returns error
|
||||
"8-5 - delete returns error",
|
||||
newVolumeArray("volume8-5", "1Gi", "uid8-5", "claim8-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete),
|
||||
withMessage("Mock delete error", newVolumeArray("volume8-5", "1Gi", "uid8-5", "claim8-5", v1.VolumeFailed, v1.PersistentVolumeReclaimDelete)),
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
||||
wrapTestWithReclaimCalls(operationDelete, []error{errors.New("Mock delete error")}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// delete success(?) - volume is deleted before doDelete() starts
|
||||
"8-6 - volume is deleted before deleting",
|
||||
newVolumeArray("volume8-6", "1Gi", "uid8-6", "claim8-6", v1.VolumeBound, v1.PersistentVolumeReclaimDelete),
|
||||
novolumes,
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||
// Delete the volume before delete operation starts
|
||||
reactor.lock.Lock()
|
||||
delete(reactor.volumes, "volume8-6")
|
||||
reactor.lock.Unlock()
|
||||
}),
|
||||
},
|
||||
{
|
||||
// delete success(?) - volume is bound just at the time doDelete()
|
||||
// starts. This simulates "volume no longer needs recycling,
|
||||
// skipping".
|
||||
"8-7 - volume is bound before deleting",
|
||||
newVolumeArray("volume8-7", "1Gi", "uid8-7", "claim8-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController),
|
||||
newVolumeArray("volume8-7", "1Gi", "uid8-7", "claim8-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController),
|
||||
noclaims,
|
||||
newClaimArray("claim8-7", "uid8-7", "10Gi", "volume8-7", v1.ClaimBound),
|
||||
noevents, noerrors,
|
||||
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||
reactor.lock.Lock()
|
||||
defer reactor.lock.Unlock()
|
||||
// Bind the volume to resurrected claim (this should never
|
||||
// happen)
|
||||
claim := newClaim("claim8-7", "uid8-7", "10Gi", "volume8-7", v1.ClaimBound)
|
||||
reactor.claims[claim.Name] = claim
|
||||
ctrl.claims.Add(claim)
|
||||
volume := reactor.volumes["volume8-7"]
|
||||
volume.Status.Phase = v1.VolumeBound
|
||||
}),
|
||||
},
|
||||
{
|
||||
// delete success - volume bound by user is deleted, while a new
|
||||
// claim is created with another UID.
|
||||
"8-9 - prebound volume is deleted while the claim exists",
|
||||
newVolumeArray("volume8-9", "1Gi", "uid8-9", "claim8-9", v1.VolumeBound, v1.PersistentVolumeReclaimDelete),
|
||||
novolumes,
|
||||
newClaimArray("claim8-9", "uid8-9-x", "10Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim8-9", "uid8-9-x", "10Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors,
|
||||
// Inject deleter into the controller and call syncVolume. The
|
||||
// deleter simulates one delete() call that succeeds.
|
||||
wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// PV requires external deleter
|
||||
"8-10 - external deleter",
|
||||
newVolumeArray("volume8-10", "1Gi", "uid10-1", "claim10-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController),
|
||||
newVolumeArray("volume8-10", "1Gi", "uid10-1", "claim10-1", v1.VolumeReleased, v1.PersistentVolumeReclaimDelete, annBoundByController),
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
// Inject external deleter annotation
|
||||
test.initialVolumes[0].Annotations[annDynamicallyProvisioned] = "external.io/test"
|
||||
test.expectedVolumes[0].Annotations[annDynamicallyProvisioned] = "external.io/test"
|
||||
return testSyncVolume(ctrl, reactor, test)
|
||||
},
|
||||
},
|
||||
{
|
||||
// delete success - two PVs are provisioned for a single claim.
|
||||
// One of the PVs is deleted.
|
||||
"8-11 - two PVs provisioned for a single claim",
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume8-11-1", "1Gi", "uid8-11", "claim8-11", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annDynamicallyProvisioned),
|
||||
newVolume("volume8-11-2", "1Gi", "uid8-11", "claim8-11", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annDynamicallyProvisioned),
|
||||
},
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume8-11-2", "1Gi", "uid8-11", "claim8-11", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annDynamicallyProvisioned),
|
||||
},
|
||||
// the claim is bound to volume8-11-2 -> volume8-11-1 has lost the race and will be deleted
|
||||
newClaimArray("claim8-11", "uid8-11", "10Gi", "volume8-11-2", v1.ClaimBound),
|
||||
newClaimArray("claim8-11", "uid8-11", "10Gi", "volume8-11-2", v1.ClaimBound),
|
||||
noevents, noerrors,
|
||||
// Inject deleter into the controller and call syncVolume. The
|
||||
// deleter simulates one delete() call that succeeds.
|
||||
wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// delete success - two PVs are externally provisioned for a single
|
||||
// claim. One of the PVs is marked as Released to be deleted by the
|
||||
// external provisioner.
|
||||
"8-12 - two PVs externally provisioned for a single claim",
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume8-12-1", "1Gi", "uid8-12", "claim8-12", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annDynamicallyProvisioned),
|
||||
newVolume("volume8-12-2", "1Gi", "uid8-12", "claim8-12", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annDynamicallyProvisioned),
|
||||
},
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume8-12-1", "1Gi", "uid8-12", "claim8-12", v1.VolumeReleased, v1.PersistentVolumeReclaimDelete, annDynamicallyProvisioned),
|
||||
newVolume("volume8-12-2", "1Gi", "uid8-12", "claim8-12", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annDynamicallyProvisioned),
|
||||
},
|
||||
// the claim is bound to volume8-12-2 -> volume8-12-1 has lost the race and will be "Released"
|
||||
newClaimArray("claim8-12", "uid8-12", "10Gi", "volume8-12-2", v1.ClaimBound),
|
||||
newClaimArray("claim8-12", "uid8-12", "10Gi", "volume8-12-2", v1.ClaimBound),
|
||||
noevents, noerrors,
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
// Inject external deleter annotation
|
||||
test.initialVolumes[0].Annotations[annDynamicallyProvisioned] = "external.io/test"
|
||||
test.expectedVolumes[0].Annotations[annDynamicallyProvisioned] = "external.io/test"
|
||||
return testSyncVolume(ctrl, reactor, test)
|
||||
},
|
||||
},
|
||||
}
|
||||
runSyncTests(t, tests, []*storage.StorageClass{})
|
||||
}
|
||||
|
||||
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
||||
// volume/claims. The test follows this pattern:
|
||||
// 0. Load the controller with initial data.
|
||||
// 1. Call controllerTest.testCall() once as in TestSync()
|
||||
// 2. For all volumes/claims changed by previous syncVolume/syncClaim calls,
|
||||
// call appropriate syncVolume/syncClaim (simulating "volume/claim changed"
|
||||
// events). Go to 2. if these calls change anything.
|
||||
// 3. When all changes are processed and no new changes were made, call
|
||||
// syncVolume/syncClaim on all volumes/claims (simulating "periodic sync").
|
||||
// 4. If some changes were done by step 3., go to 2. (simulation of
|
||||
// "volume/claim updated" events, eventually performing step 3. again)
|
||||
// 5. When 3. does not do any changes, finish the tests and compare final set
|
||||
// of volumes/claims with expected claims/volumes and report differences.
|
||||
// Some limit of calls in enforced to prevent endless loops.
|
||||
func TestDeleteMultiSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
{
|
||||
// delete failure - delete returns error. The controller should
|
||||
// try again.
|
||||
"9-1 - delete returns error",
|
||||
newVolumeArray("volume9-1", "1Gi", "uid9-1", "claim9-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete),
|
||||
novolumes,
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeFailedDelete"}, noerrors,
|
||||
wrapTestWithReclaimCalls(operationDelete, []error{errors.New("Mock delete error"), nil}, testSyncVolume),
|
||||
},
|
||||
}
|
||||
|
||||
runMultisyncTests(t, tests, []*storage.StorageClass{}, "")
|
||||
}
|
1243
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/framework_test.go
generated
vendored
Normal file
1243
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/framework_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
282
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index.go
generated
vendored
Normal file
282
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index.go
generated
vendored
Normal file
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
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 persistentvolume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/v1beta1/util"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
)
|
||||
|
||||
// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes
|
||||
// indexed by AccessModes and ordered by storage capacity.
|
||||
type persistentVolumeOrderedIndex struct {
|
||||
store cache.Indexer
|
||||
}
|
||||
|
||||
func newPersistentVolumeOrderedIndex() persistentVolumeOrderedIndex {
|
||||
return persistentVolumeOrderedIndex{cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"accessmodes": accessModesIndexFunc})}
|
||||
}
|
||||
|
||||
// accessModesIndexFunc is an indexing function that returns a persistent
|
||||
// volume's AccessModes as a string
|
||||
func accessModesIndexFunc(obj interface{}) ([]string, error) {
|
||||
if pv, ok := obj.(*v1.PersistentVolume); ok {
|
||||
modes := v1.GetAccessModesAsString(pv.Spec.AccessModes)
|
||||
return []string{modes}, nil
|
||||
}
|
||||
return []string{""}, fmt.Errorf("object is not a persistent volume: %v", obj)
|
||||
}
|
||||
|
||||
// listByAccessModes returns all volumes with the given set of
|
||||
// AccessModeTypes. The list is unsorted!
|
||||
func (pvIndex *persistentVolumeOrderedIndex) listByAccessModes(modes []v1.PersistentVolumeAccessMode) ([]*v1.PersistentVolume, error) {
|
||||
pv := &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
AccessModes: modes,
|
||||
},
|
||||
}
|
||||
|
||||
objs, err := pvIndex.store.Index("accessmodes", pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumes := make([]*v1.PersistentVolume, len(objs))
|
||||
for i, obj := range objs {
|
||||
volumes[i] = obj.(*v1.PersistentVolume)
|
||||
}
|
||||
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
// matchPredicate is a function that indicates that a persistent volume matches another
|
||||
type matchPredicate func(compareThis, toThis *v1.PersistentVolume) bool
|
||||
|
||||
// find returns the nearest PV from the ordered list or nil if a match is not found
|
||||
func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *v1.PersistentVolumeClaim, matchPredicate matchPredicate) (*v1.PersistentVolume, error) {
|
||||
// PVs are indexed by their access modes to allow easier searching. Each
|
||||
// index is the string representation of a set of access modes. There is a
|
||||
// finite number of possible sets and PVs will only be indexed in one of
|
||||
// them (whichever index matches the PV's modes).
|
||||
//
|
||||
// A request for resources will always specify its desired access modes.
|
||||
// Any matching PV must have at least that number of access modes, but it
|
||||
// can have more. For example, a user asks for ReadWriteOnce but a GCEPD
|
||||
// is available, which is ReadWriteOnce+ReadOnlyMany.
|
||||
//
|
||||
// Searches are performed against a set of access modes, so we can attempt
|
||||
// not only the exact matching modes but also potential matches (the GCEPD
|
||||
// example above).
|
||||
allPossibleModes := pvIndex.allPossibleMatchingAccessModes(claim.Spec.AccessModes)
|
||||
|
||||
var smallestVolume *v1.PersistentVolume
|
||||
var smallestVolumeSize int64
|
||||
requestedQty := claim.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
|
||||
requestedSize := requestedQty.Value()
|
||||
requestedClass := storageutil.GetClaimStorageClass(claim)
|
||||
|
||||
var selector labels.Selector
|
||||
if claim.Spec.Selector != nil {
|
||||
internalSelector, err := metav1.LabelSelectorAsSelector(claim.Spec.Selector)
|
||||
if err != nil {
|
||||
// should be unreachable code due to validation
|
||||
return nil, fmt.Errorf("error creating internal label selector for claim: %v: %v", claimToClaimKey(claim), err)
|
||||
}
|
||||
selector = internalSelector
|
||||
}
|
||||
|
||||
for _, modes := range allPossibleModes {
|
||||
volumes, err := pvIndex.listByAccessModes(modes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Go through all available volumes with two goals:
|
||||
// - find a volume that is either pre-bound by user or dynamically
|
||||
// provisioned for this claim. Because of this we need to loop through
|
||||
// all volumes.
|
||||
// - find the smallest matching one if there is no volume pre-bound to
|
||||
// the claim.
|
||||
for _, volume := range volumes {
|
||||
if isVolumeBoundToClaim(volume, claim) {
|
||||
// this claim and volume are pre-bound; return
|
||||
// the volume if the size request is satisfied,
|
||||
// otherwise continue searching for a match
|
||||
volumeQty := volume.Spec.Capacity[v1.ResourceStorage]
|
||||
volumeSize := volumeQty.Value()
|
||||
if volumeSize < requestedSize {
|
||||
continue
|
||||
}
|
||||
return volume, nil
|
||||
}
|
||||
|
||||
// In Alpha dynamic provisioning, we do now want not match claims
|
||||
// with existing PVs, findByClaim must find only PVs that are
|
||||
// pre-bound to the claim (by dynamic provisioning). TODO: remove in
|
||||
// 1.5
|
||||
if v1.HasAnnotation(claim.ObjectMeta, storageutil.AlphaStorageClassAnnotation) {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter out:
|
||||
// - volumes bound to another claim
|
||||
// - volumes whose labels don't match the claim's selector, if specified
|
||||
// - volumes in Class that is not requested
|
||||
if volume.Spec.ClaimRef != nil {
|
||||
continue
|
||||
} else if selector != nil && !selector.Matches(labels.Set(volume.Labels)) {
|
||||
continue
|
||||
}
|
||||
if storageutil.GetVolumeStorageClass(volume) != requestedClass {
|
||||
continue
|
||||
}
|
||||
|
||||
volumeQty := volume.Spec.Capacity[v1.ResourceStorage]
|
||||
volumeSize := volumeQty.Value()
|
||||
if volumeSize >= requestedSize {
|
||||
if smallestVolume == nil || smallestVolumeSize > volumeSize {
|
||||
smallestVolume = volume
|
||||
smallestVolumeSize = volumeSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if smallestVolume != nil {
|
||||
// Found a matching volume
|
||||
return smallestVolume, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// findBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage
|
||||
func (pvIndex *persistentVolumeOrderedIndex) findBestMatchForClaim(claim *v1.PersistentVolumeClaim) (*v1.PersistentVolume, error) {
|
||||
return pvIndex.findByClaim(claim, matchStorageCapacity)
|
||||
}
|
||||
|
||||
// matchStorageCapacity is a matchPredicate used to sort and find volumes
|
||||
func matchStorageCapacity(pvA, pvB *v1.PersistentVolume) bool {
|
||||
aQty := pvA.Spec.Capacity[v1.ResourceStorage]
|
||||
bQty := pvB.Spec.Capacity[v1.ResourceStorage]
|
||||
aSize := aQty.Value()
|
||||
bSize := bQty.Value()
|
||||
return aSize <= bSize
|
||||
}
|
||||
|
||||
// allPossibleMatchingAccessModes returns an array of AccessMode arrays that
|
||||
// can satisfy a user's requested modes.
|
||||
//
|
||||
// see comments in the Find func above regarding indexing.
|
||||
//
|
||||
// allPossibleMatchingAccessModes gets all stringified accessmodes from the
|
||||
// index and returns all those that contain at least all of the requested
|
||||
// mode.
|
||||
//
|
||||
// For example, assume the index contains 2 types of PVs where the stringified
|
||||
// accessmodes are:
|
||||
//
|
||||
// "RWO,ROX" -- some number of GCEPDs
|
||||
// "RWO,ROX,RWX" -- some number of NFS volumes
|
||||
//
|
||||
// A request for RWO could be satisfied by both sets of indexed volumes, so
|
||||
// allPossibleMatchingAccessModes returns:
|
||||
//
|
||||
// [][]v1.PersistentVolumeAccessMode {
|
||||
// []v1.PersistentVolumeAccessMode {
|
||||
// v1.ReadWriteOnce, v1.ReadOnlyMany,
|
||||
// },
|
||||
// []v1.PersistentVolumeAccessMode {
|
||||
// v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// A request for RWX can be satisfied by only one set of indexed volumes, so
|
||||
// the return is:
|
||||
//
|
||||
// [][]v1.PersistentVolumeAccessMode {
|
||||
// []v1.PersistentVolumeAccessMode {
|
||||
// v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// This func returns modes with ascending levels of modes to give the user
|
||||
// what is closest to what they actually asked for.
|
||||
func (pvIndex *persistentVolumeOrderedIndex) allPossibleMatchingAccessModes(requestedModes []v1.PersistentVolumeAccessMode) [][]v1.PersistentVolumeAccessMode {
|
||||
matchedModes := [][]v1.PersistentVolumeAccessMode{}
|
||||
keys := pvIndex.store.ListIndexFuncValues("accessmodes")
|
||||
for _, key := range keys {
|
||||
indexedModes := v1.GetAccessModesFromString(key)
|
||||
if containedInAll(indexedModes, requestedModes) {
|
||||
matchedModes = append(matchedModes, indexedModes)
|
||||
}
|
||||
}
|
||||
|
||||
// sort by the number of modes in each array with the fewest number of
|
||||
// modes coming first. this allows searching for volumes by the minimum
|
||||
// number of modes required of the possible matches.
|
||||
sort.Sort(byAccessModes{matchedModes})
|
||||
return matchedModes
|
||||
}
|
||||
|
||||
func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
||||
for _, m := range modes {
|
||||
if m == mode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containedInAll(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool {
|
||||
for _, mode := range requestedModes {
|
||||
if !contains(indexedModes, mode) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// byAccessModes is used to order access modes by size, with the fewest modes first
|
||||
type byAccessModes struct {
|
||||
modes [][]v1.PersistentVolumeAccessMode
|
||||
}
|
||||
|
||||
func (c byAccessModes) Less(i, j int) bool {
|
||||
return len(c.modes[i]) < len(c.modes[j])
|
||||
}
|
||||
|
||||
func (c byAccessModes) Swap(i, j int) {
|
||||
c.modes[i], c.modes[j] = c.modes[j], c.modes[i]
|
||||
}
|
||||
|
||||
func (c byAccessModes) Len() int {
|
||||
return len(c.modes)
|
||||
}
|
||||
|
||||
func claimToClaimKey(claim *v1.PersistentVolumeClaim) string {
|
||||
return fmt.Sprintf("%s/%s", claim.Namespace, claim.Name)
|
||||
}
|
||||
|
||||
func claimrefToClaimKey(claimref *v1.ObjectReference) string {
|
||||
return fmt.Sprintf("%s/%s", claimref.Namespace, claimref.Name)
|
||||
}
|
786
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index_test.go
generated
vendored
Normal file
786
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index_test.go
generated
vendored
Normal file
|
@ -0,0 +1,786 @@
|
|||
/*
|
||||
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 persistentvolume
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/v1beta1/util"
|
||||
)
|
||||
|
||||
func TestMatchVolume(t *testing.T) {
|
||||
volList := newPersistentVolumeOrderedIndex()
|
||||
for _, pv := range createTestVolumes() {
|
||||
volList.store.Add(pv)
|
||||
}
|
||||
|
||||
scenarios := map[string]struct {
|
||||
expectedMatch string
|
||||
claim *v1.PersistentVolumeClaim
|
||||
}{
|
||||
"successful-match-gce-10": {
|
||||
expectedMatch: "gce-pd-10",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("8G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"successful-match-nfs-5": {
|
||||
expectedMatch: "nfs-5",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce, v1.ReadWriteMany},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"successful-skip-1g-bound-volume": {
|
||||
expectedMatch: "gce-pd-5",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"successful-no-match": {
|
||||
expectedMatch: "",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("999G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"successful-no-match-due-to-label": {
|
||||
expectedMatch: "",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"should-not-exist": "true",
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("999G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"successful-no-match-due-to-size-constraint-with-label-selector": {
|
||||
expectedMatch: "",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"should-exist": "true",
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("20000G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"successful-match-due-with-constraint-and-label-selector": {
|
||||
expectedMatch: "gce-pd-2",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"should-exist": "true",
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("20000G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"successful-match-with-class": {
|
||||
expectedMatch: "gce-pd-silver1",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
Annotations: map[string]string{
|
||||
storageutil.StorageClassAnnotation: "silver",
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"should-exist": "true",
|
||||
},
|
||||
},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"successful-match-with-class-and-labels": {
|
||||
expectedMatch: "gce-pd-silver2",
|
||||
claim: &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
Annotations: map[string]string{
|
||||
storageutil.StorageClassAnnotation: "silver",
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
volume, err := volList.findBestMatchForClaim(scenario.claim)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error matching volume by claim: %v", err)
|
||||
}
|
||||
if len(scenario.expectedMatch) != 0 && volume == nil {
|
||||
t.Errorf("Expected match but received nil volume for scenario: %s", name)
|
||||
}
|
||||
if len(scenario.expectedMatch) != 0 && volume != nil && string(volume.UID) != scenario.expectedMatch {
|
||||
t.Errorf("Expected %s but got volume %s in scenario %s", scenario.expectedMatch, volume.UID, name)
|
||||
}
|
||||
if len(scenario.expectedMatch) == 0 && volume != nil {
|
||||
t.Errorf("Unexpected match for scenario: %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchingWithBoundVolumes(t *testing.T) {
|
||||
volumeIndex := newPersistentVolumeOrderedIndex()
|
||||
// two similar volumes, one is bound
|
||||
pv1 := &v1.PersistentVolume{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-1",
|
||||
Name: "gce001",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany},
|
||||
// this one we're pretending is already bound
|
||||
ClaimRef: &v1.ObjectReference{UID: "abc123"},
|
||||
},
|
||||
}
|
||||
|
||||
pv2 := &v1.PersistentVolume{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-2",
|
||||
Name: "gce002",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany},
|
||||
},
|
||||
}
|
||||
|
||||
volumeIndex.store.Add(pv1)
|
||||
volumeIndex.store.Add(pv2)
|
||||
|
||||
claim := &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
volume, err := volumeIndex.findBestMatchForClaim(claim)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error matching volume by claim: %v", err)
|
||||
}
|
||||
if volume == nil {
|
||||
t.Fatalf("Unexpected nil volume. Expected %s", pv2.Name)
|
||||
}
|
||||
if pv2.Name != volume.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", pv2.Name, volume.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListByAccessModes(t *testing.T) {
|
||||
volList := newPersistentVolumeOrderedIndex()
|
||||
for _, pv := range createTestVolumes() {
|
||||
volList.store.Add(pv)
|
||||
}
|
||||
|
||||
volumes, err := volList.listByAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany})
|
||||
if err != nil {
|
||||
t.Error("Unexpected error retrieving volumes by access modes:", err)
|
||||
}
|
||||
sort.Sort(byCapacity{volumes})
|
||||
|
||||
for i, expected := range []string{"gce-pd-1", "gce-pd-5", "gce-pd-10"} {
|
||||
if string(volumes[i].UID) != expected {
|
||||
t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID)
|
||||
}
|
||||
}
|
||||
|
||||
volumes, err = volList.listByAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany})
|
||||
if err != nil {
|
||||
t.Error("Unexpected error retrieving volumes by access modes:", err)
|
||||
}
|
||||
sort.Sort(byCapacity{volumes})
|
||||
|
||||
for i, expected := range []string{"nfs-1", "nfs-5", "nfs-10"} {
|
||||
if string(volumes[i].UID) != expected {
|
||||
t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllPossibleAccessModes(t *testing.T) {
|
||||
index := newPersistentVolumeOrderedIndex()
|
||||
for _, pv := range createTestVolumes() {
|
||||
index.store.Add(pv)
|
||||
}
|
||||
|
||||
// the mock PVs creates contain 2 types of accessmodes: RWO+ROX and RWO+ROW+RWX
|
||||
possibleModes := index.allPossibleMatchingAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce})
|
||||
if len(possibleModes) != 3 {
|
||||
t.Errorf("Expected 3 arrays of modes that match RWO, but got %v", len(possibleModes))
|
||||
}
|
||||
for _, m := range possibleModes {
|
||||
if !contains(m, v1.ReadWriteOnce) {
|
||||
t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
|
||||
}
|
||||
}
|
||||
|
||||
possibleModes = index.allPossibleMatchingAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteMany})
|
||||
if len(possibleModes) != 1 {
|
||||
t.Errorf("Expected 1 array of modes that match RWX, but got %v", len(possibleModes))
|
||||
}
|
||||
if !contains(possibleModes[0], v1.ReadWriteMany) {
|
||||
t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
|
||||
gce := &v1.PersistentVolume{
|
||||
ObjectMeta: v1.ObjectMeta{UID: "001", Name: "gce"},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ebs := &v1.PersistentVolume{
|
||||
ObjectMeta: v1.ObjectMeta{UID: "002", Name: "ebs"},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{}},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
nfs := &v1.PersistentVolume{
|
||||
ObjectMeta: v1.ObjectMeta{UID: "003", Name: "nfs"},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{NFS: &v1.NFSVolumeSource{}},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
v1.ReadWriteMany,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
claim := &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")}},
|
||||
},
|
||||
}
|
||||
|
||||
index := newPersistentVolumeOrderedIndex()
|
||||
index.store.Add(gce)
|
||||
index.store.Add(ebs)
|
||||
index.store.Add(nfs)
|
||||
|
||||
volume, _ := index.findBestMatchForClaim(claim)
|
||||
if volume.Name != ebs.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", ebs.Name, volume.Name)
|
||||
}
|
||||
|
||||
claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany}
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != gce.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
|
||||
}
|
||||
|
||||
// order of the requested modes should not matter
|
||||
claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce, v1.ReadOnlyMany}
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != nfs.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
|
||||
}
|
||||
|
||||
// fewer modes requested should still match
|
||||
claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != nfs.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
|
||||
}
|
||||
|
||||
// pretend the exact match is bound. should get the next level up of modes.
|
||||
ebs.Spec.ClaimRef = &v1.ObjectReference{}
|
||||
claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != gce.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
|
||||
}
|
||||
|
||||
// continue up the levels of modes.
|
||||
gce.Spec.ClaimRef = &v1.ObjectReference{}
|
||||
claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != nfs.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
|
||||
}
|
||||
|
||||
// partial mode request
|
||||
gce.Spec.ClaimRef = nil
|
||||
claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != gce.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestVolumes() []*v1.PersistentVolume {
|
||||
// these volumes are deliberately out-of-order to test indexing and sorting
|
||||
return []*v1.PersistentVolume{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-10",
|
||||
Name: "gce003",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-20",
|
||||
Name: "gce004",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("20G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
},
|
||||
// this one we're pretending is already bound
|
||||
ClaimRef: &v1.ObjectReference{UID: "def456"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "nfs-5",
|
||||
Name: "nfs002",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
Glusterfs: &v1.GlusterfsVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
v1.ReadWriteMany,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-1",
|
||||
Name: "gce001",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
},
|
||||
// this one we're pretending is already bound
|
||||
ClaimRef: &v1.ObjectReference{UID: "abc123"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "nfs-10",
|
||||
Name: "nfs003",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
Glusterfs: &v1.GlusterfsVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
v1.ReadWriteMany,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-5",
|
||||
Name: "gce002",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "nfs-1",
|
||||
Name: "nfs001",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
Glusterfs: &v1.GlusterfsVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
v1.ReadWriteMany,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-2",
|
||||
Name: "gce0022",
|
||||
Labels: map[string]string{
|
||||
"should-exist": "true",
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("20000G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-silver1",
|
||||
Name: "gce0023",
|
||||
Labels: map[string]string{
|
||||
"should-exist": "true",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
storageutil.StorageClassAnnotation: "silver",
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("10000G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-silver2",
|
||||
Name: "gce0024",
|
||||
Annotations: map[string]string{
|
||||
storageutil.StorageClassAnnotation: "silver",
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "gce-pd-gold",
|
||||
Name: "gce0025",
|
||||
Annotations: map[string]string{
|
||||
storageutil.StorageClassAnnotation: "gold",
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("50G"),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testVolume(name, size string) *v1.PersistentVolume {
|
||||
return &v1.PersistentVolume{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: name,
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse(size)},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{}},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindingPreboundVolumes(t *testing.T) {
|
||||
claim := &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "claim01",
|
||||
Namespace: "myns",
|
||||
SelfLink: testapi.Default.SelfLink("pvc", ""),
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1Gi")}},
|
||||
},
|
||||
}
|
||||
claimRef, err := v1.GetReference(claim)
|
||||
if err != nil {
|
||||
t.Errorf("error getting claimRef: %v", err)
|
||||
}
|
||||
|
||||
pv1 := testVolume("pv1", "1Gi")
|
||||
pv5 := testVolume("pv5", "5Gi")
|
||||
pv8 := testVolume("pv8", "8Gi")
|
||||
pvBadSize := testVolume("pvBadSize", "1Mi")
|
||||
pvBadMode := testVolume("pvBadMode", "1Gi")
|
||||
pvBadMode.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
|
||||
|
||||
index := newPersistentVolumeOrderedIndex()
|
||||
index.store.Add(pv1)
|
||||
index.store.Add(pv5)
|
||||
index.store.Add(pv8)
|
||||
index.store.Add(pvBadSize)
|
||||
index.store.Add(pvBadMode)
|
||||
|
||||
// expected exact match on size
|
||||
volume, _ := index.findBestMatchForClaim(claim)
|
||||
if volume.Name != pv1.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
|
||||
}
|
||||
|
||||
// pretend the exact match is pre-bound. should get the next size up.
|
||||
pv1.Spec.ClaimRef = &v1.ObjectReference{Name: "foo", Namespace: "bar"}
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != pv5.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", pv5.Name, volume.Name)
|
||||
}
|
||||
|
||||
// pretend the exact match is available but the largest volume is pre-bound to the claim.
|
||||
pv1.Spec.ClaimRef = nil
|
||||
pv8.Spec.ClaimRef = claimRef
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != pv8.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name)
|
||||
}
|
||||
|
||||
// pretend the volume with too small a size is pre-bound to the claim. should get the exact match.
|
||||
pv8.Spec.ClaimRef = nil
|
||||
pvBadSize.Spec.ClaimRef = claimRef
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != pv1.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
|
||||
}
|
||||
|
||||
// pretend the volume without the right access mode is pre-bound to the claim. should get the exact match.
|
||||
pvBadSize.Spec.ClaimRef = nil
|
||||
pvBadMode.Spec.ClaimRef = claimRef
|
||||
volume, _ = index.findBestMatchForClaim(claim)
|
||||
if volume.Name != pv1.Name {
|
||||
t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// byCapacity is used to order volumes by ascending storage size
|
||||
type byCapacity struct {
|
||||
volumes []*v1.PersistentVolume
|
||||
}
|
||||
|
||||
func (c byCapacity) Less(i, j int) bool {
|
||||
return matchStorageCapacity(c.volumes[i], c.volumes[j])
|
||||
}
|
||||
|
||||
func (c byCapacity) Swap(i, j int) {
|
||||
c.volumes[i], c.volumes[j] = c.volumes[j], c.volumes[i]
|
||||
}
|
||||
|
||||
func (c byCapacity) Len() int {
|
||||
return len(c.volumes)
|
||||
}
|
28
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/options/BUILD
generated
vendored
Normal file
28
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/options/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["options.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//vendor:github.com/spf13/pflag"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
91
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/options/options.go
generated
vendored
Normal file
91
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/options/options.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package options
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// VolumeConfigFlags is used to bind CLI flags to variables. This top-level struct contains *all* enumerated
|
||||
// CLI flags meant to configure all volume plugins. From this config, the binary will create many instances
|
||||
// of volume.VolumeConfig which are then passed to the appropriate plugin. The ControllerManager binary is the only
|
||||
// part of the code which knows what plugins are supported and which CLI flags correspond to each plugin.
|
||||
type VolumeConfigFlags struct {
|
||||
PersistentVolumeRecyclerMaximumRetry int
|
||||
PersistentVolumeRecyclerMinimumTimeoutNFS int
|
||||
PersistentVolumeRecyclerPodTemplateFilePathNFS string
|
||||
PersistentVolumeRecyclerIncrementTimeoutNFS int
|
||||
PersistentVolumeRecyclerPodTemplateFilePathHostPath string
|
||||
PersistentVolumeRecyclerMinimumTimeoutHostPath int
|
||||
PersistentVolumeRecyclerIncrementTimeoutHostPath int
|
||||
EnableHostPathProvisioning bool
|
||||
EnableDynamicProvisioning bool
|
||||
}
|
||||
|
||||
type PersistentVolumeControllerOptions struct {
|
||||
PVClaimBinderSyncPeriod time.Duration
|
||||
VolumeConfigFlags VolumeConfigFlags
|
||||
}
|
||||
|
||||
func NewPersistentVolumeControllerOptions() PersistentVolumeControllerOptions {
|
||||
return PersistentVolumeControllerOptions{
|
||||
PVClaimBinderSyncPeriod: 15 * time.Second,
|
||||
VolumeConfigFlags: VolumeConfigFlags{
|
||||
// default values here
|
||||
PersistentVolumeRecyclerMaximumRetry: 3,
|
||||
PersistentVolumeRecyclerMinimumTimeoutNFS: 300,
|
||||
PersistentVolumeRecyclerIncrementTimeoutNFS: 30,
|
||||
PersistentVolumeRecyclerMinimumTimeoutHostPath: 60,
|
||||
PersistentVolumeRecyclerIncrementTimeoutHostPath: 30,
|
||||
EnableHostPathProvisioning: false,
|
||||
EnableDynamicProvisioning: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *PersistentVolumeControllerOptions) AddFlags(fs *pflag.FlagSet) {
|
||||
fs.DurationVar(&o.PVClaimBinderSyncPeriod, "pvclaimbinder-sync-period", o.PVClaimBinderSyncPeriod,
|
||||
"The period for syncing persistent volumes and persistent volume claims")
|
||||
fs.StringVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerPodTemplateFilePathNFS,
|
||||
"pv-recycler-pod-template-filepath-nfs", o.VolumeConfigFlags.PersistentVolumeRecyclerPodTemplateFilePathNFS,
|
||||
"The file path to a pod definition used as a template for NFS persistent volume recycling")
|
||||
fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerMinimumTimeoutNFS, "pv-recycler-minimum-timeout-nfs",
|
||||
o.VolumeConfigFlags.PersistentVolumeRecyclerMinimumTimeoutNFS, "The minimum ActiveDeadlineSeconds to use for an NFS Recycler pod")
|
||||
fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerIncrementTimeoutNFS, "pv-recycler-increment-timeout-nfs",
|
||||
o.VolumeConfigFlags.PersistentVolumeRecyclerIncrementTimeoutNFS, "the increment of time added per Gi to ActiveDeadlineSeconds for an NFS scrubber pod")
|
||||
fs.StringVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerPodTemplateFilePathHostPath, "pv-recycler-pod-template-filepath-hostpath",
|
||||
o.VolumeConfigFlags.PersistentVolumeRecyclerPodTemplateFilePathHostPath,
|
||||
"The file path to a pod definition used as a template for HostPath persistent volume recycling. "+
|
||||
"This is for development and testing only and will not work in a multi-node cluster.")
|
||||
fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerMinimumTimeoutHostPath, "pv-recycler-minimum-timeout-hostpath",
|
||||
o.VolumeConfigFlags.PersistentVolumeRecyclerMinimumTimeoutHostPath,
|
||||
"The minimum ActiveDeadlineSeconds to use for a HostPath Recycler pod. This is for development and testing only and will not work in a multi-node cluster.")
|
||||
fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerIncrementTimeoutHostPath, "pv-recycler-timeout-increment-hostpath",
|
||||
o.VolumeConfigFlags.PersistentVolumeRecyclerIncrementTimeoutHostPath,
|
||||
"the increment of time added per Gi to ActiveDeadlineSeconds for a HostPath scrubber pod. "+
|
||||
"This is for development and testing only and will not work in a multi-node cluster.")
|
||||
fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerMaximumRetry, "pv-recycler-maximum-retry",
|
||||
o.VolumeConfigFlags.PersistentVolumeRecyclerMaximumRetry,
|
||||
"Maximum number of attempts to recycle or delete a persistent volume")
|
||||
fs.BoolVar(&o.VolumeConfigFlags.EnableHostPathProvisioning, "enable-hostpath-provisioner", o.VolumeConfigFlags.EnableHostPathProvisioning,
|
||||
"Enable HostPath PV provisioning when running without a cloud provider. This allows testing and development of provisioning features. "+
|
||||
"HostPath provisioning is not supported in any way, won't work in a multi-node cluster, and should not be used for anything other than testing or development.")
|
||||
fs.BoolVar(&o.VolumeConfigFlags.EnableDynamicProvisioning, "enable-dynamic-provisioning", o.VolumeConfigFlags.EnableDynamicProvisioning,
|
||||
"Enable dynamic provisioning for environments that support it.")
|
||||
}
|
430
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/provision_test.go
generated
vendored
Normal file
430
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/provision_test.go
generated
vendored
Normal file
|
@ -0,0 +1,430 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package persistentvolume
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
storage "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
|
||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/v1beta1/util"
|
||||
)
|
||||
|
||||
var class1Parameters = map[string]string{
|
||||
"param1": "value1",
|
||||
}
|
||||
var class2Parameters = map[string]string{
|
||||
"param2": "value2",
|
||||
}
|
||||
var storageClasses = []*storage.StorageClass{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "gold",
|
||||
},
|
||||
|
||||
Provisioner: mockPluginName,
|
||||
Parameters: class1Parameters,
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "silver",
|
||||
},
|
||||
Provisioner: mockPluginName,
|
||||
Parameters: class2Parameters,
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "external",
|
||||
},
|
||||
Provisioner: "vendor.com/my-volume",
|
||||
Parameters: class1Parameters,
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "unknown-internal",
|
||||
},
|
||||
Provisioner: "kubernetes.io/unknown",
|
||||
Parameters: class1Parameters,
|
||||
},
|
||||
}
|
||||
|
||||
// call to storageClass 1, returning an error
|
||||
var provision1Error = provisionCall{
|
||||
ret: errors.New("Mock provisioner error"),
|
||||
expectedParameters: class1Parameters,
|
||||
}
|
||||
|
||||
// call to storageClass 1, returning a valid PV
|
||||
var provision1Success = provisionCall{
|
||||
ret: nil,
|
||||
expectedParameters: class1Parameters,
|
||||
}
|
||||
|
||||
// call to storageClass 2, returning a valid PV
|
||||
var provision2Success = provisionCall{
|
||||
ret: nil,
|
||||
expectedParameters: class2Parameters,
|
||||
}
|
||||
|
||||
var provisionAlphaSuccess = provisionCall{
|
||||
ret: nil,
|
||||
}
|
||||
|
||||
// Test single call to syncVolume, expecting provisioning to happen.
|
||||
// 1. Fill in the controller with initial data
|
||||
// 2. Call the syncVolume *once*.
|
||||
// 3. Compare resulting volumes with expected volumes.
|
||||
func TestProvisionSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
{
|
||||
// Provision a volume (with a default class)
|
||||
"11-1 - successful provision with storage class 1",
|
||||
novolumes,
|
||||
newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-1", "uid11-1", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
// Binding will be completed in the next syncClaim
|
||||
newClaimArray("claim11-1", "uid11-1", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
[]string{"Normal ProvisioningSucceeded"}, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// Provision failure - plugin not found
|
||||
"11-2 - plugin not found",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim11-2", "uid11-2", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-2", "uid11-2", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
[]string{"Warning ProvisioningFailed"}, noerrors,
|
||||
testSyncClaim,
|
||||
},
|
||||
{
|
||||
// Provision failure - newProvisioner returns error
|
||||
"11-3 - newProvisioner failure",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim11-3", "uid11-3", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-3", "uid11-3", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
[]string{"Warning ProvisioningFailed"}, noerrors,
|
||||
wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// Provision failure - Provision returns error
|
||||
"11-4 - provision failure",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim11-4", "uid11-4", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-4", "uid11-4", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
[]string{"Warning ProvisioningFailed"}, noerrors,
|
||||
wrapTestWithProvisionCalls([]provisionCall{provision1Error}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// No provisioning if there is a matching volume available
|
||||
"11-6 - provisioning when there is a volume available",
|
||||
newVolumeArray("volume11-6", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain, storageutil.StorageClassAnnotation),
|
||||
newVolumeArray("volume11-6", "1Gi", "uid11-6", "claim11-6", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-6", "uid11-6", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-6", "uid11-6", "1Gi", "volume11-6", v1.ClaimBound, storageutil.StorageClassAnnotation, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors,
|
||||
// No provisioning plugin confingure - makes the test fail when
|
||||
// the controller errorneously tries to provision something
|
||||
wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// Provision success? - claim is bound before provisioner creates
|
||||
// a volume.
|
||||
"11-7 - claim is bound before provisioning",
|
||||
novolumes,
|
||||
newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-7", "uid11-7", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
// The claim would be bound in next syncClaim
|
||||
newClaimArray("claim11-7", "uid11-7", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
noevents, noerrors,
|
||||
wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||
// Create a volume before provisionClaimOperation starts.
|
||||
// This similates a parallel controller provisioning the volume.
|
||||
reactor.lock.Lock()
|
||||
volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation)
|
||||
reactor.volumes[volume.Name] = volume
|
||||
reactor.lock.Unlock()
|
||||
}),
|
||||
},
|
||||
{
|
||||
// Provision success - cannot save provisioned PV once,
|
||||
// second retry succeeds
|
||||
"11-8 - cannot save provisioned volume",
|
||||
novolumes,
|
||||
newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-8", "uid11-8", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
// Binding will be completed in the next syncClaim
|
||||
newClaimArray("claim11-8", "uid11-8", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
[]string{"Normal ProvisioningSucceeded"},
|
||||
[]reactorError{
|
||||
// Inject error to the first
|
||||
// kubeclient.PersistentVolumes.Create() call. All other calls
|
||||
// will succeed.
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error")},
|
||||
},
|
||||
wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// Provision success? - cannot save provisioned PV five times,
|
||||
// volume is deleted and delete succeeds
|
||||
"11-9 - cannot save provisioned volume, delete succeeds",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim11-9", "uid11-9", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-9", "uid11-9", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
[]string{"Warning ProvisioningFailed"},
|
||||
[]reactorError{
|
||||
// Inject error to five kubeclient.PersistentVolumes.Create()
|
||||
// calls
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error1")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error2")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error3")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
||||
},
|
||||
wrapTestWithPluginCalls(
|
||||
nil, // recycle calls
|
||||
[]error{nil}, // delete calls
|
||||
[]provisionCall{provision1Success}, // provision calls
|
||||
testSyncClaim,
|
||||
),
|
||||
},
|
||||
{
|
||||
// Provision failure - cannot save provisioned PV five times,
|
||||
// volume delete failed - no plugin found
|
||||
"11-10 - cannot save provisioned volume, no delete plugin found",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim11-10", "uid11-10", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-10", "uid11-10", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
[]string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"},
|
||||
[]reactorError{
|
||||
// Inject error to five kubeclient.PersistentVolumes.Create()
|
||||
// calls
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error1")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error2")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error3")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
||||
},
|
||||
// No deleteCalls are configured, which results into no deleter plugin available for the volume
|
||||
wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// Provision failure - cannot save provisioned PV five times,
|
||||
// volume delete failed - deleter returns error five times
|
||||
"11-11 - cannot save provisioned volume, deleter fails",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim11-11", "uid11-11", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-11", "uid11-11", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
[]string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"},
|
||||
[]reactorError{
|
||||
// Inject error to five kubeclient.PersistentVolumes.Create()
|
||||
// calls
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error1")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error2")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error3")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
||||
},
|
||||
wrapTestWithPluginCalls(
|
||||
nil, // recycle calls
|
||||
[]error{ // delete calls
|
||||
errors.New("Mock deletion error1"),
|
||||
errors.New("Mock deletion error2"),
|
||||
errors.New("Mock deletion error3"),
|
||||
errors.New("Mock deletion error4"),
|
||||
errors.New("Mock deletion error5"),
|
||||
},
|
||||
[]provisionCall{provision1Success}, // provision calls
|
||||
testSyncClaim),
|
||||
},
|
||||
{
|
||||
// Provision failure - cannot save provisioned PV five times,
|
||||
// volume delete succeeds 2nd time
|
||||
"11-12 - cannot save provisioned volume, delete succeeds 2nd time",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim11-12", "uid11-12", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim11-12", "uid11-12", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation, annStorageProvisioner),
|
||||
[]string{"Warning ProvisioningFailed"},
|
||||
[]reactorError{
|
||||
// Inject error to five kubeclient.PersistentVolumes.Create()
|
||||
// calls
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error1")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error2")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error3")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error4")},
|
||||
{"create", "persistentvolumes", errors.New("Mock creation error5")},
|
||||
},
|
||||
wrapTestWithPluginCalls(
|
||||
nil, // recycle calls
|
||||
[]error{ // delete calls
|
||||
errors.New("Mock deletion error1"),
|
||||
nil,
|
||||
}, // provison calls
|
||||
[]provisionCall{provision1Success},
|
||||
testSyncClaim,
|
||||
),
|
||||
},
|
||||
{
|
||||
// Provision a volume (with non-default class)
|
||||
"11-13 - successful provision with storage class 2",
|
||||
novolumes,
|
||||
volumeWithClass("silver", newVolumeArray("pvc-uid11-13", "1Gi", "uid11-13", "claim11-13", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned)),
|
||||
claimWithClass("silver", newClaimArray("claim11-13", "uid11-13", "1Gi", "", v1.ClaimPending)),
|
||||
// Binding will be completed in the next syncClaim
|
||||
claimWithClass("silver", newClaimArray("claim11-13", "uid11-13", "1Gi", "", v1.ClaimPending, annStorageProvisioner)),
|
||||
[]string{"Normal ProvisioningSucceeded"}, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision2Success}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// Provision error - non existing class
|
||||
"11-14 - fail due to non-existing class",
|
||||
novolumes,
|
||||
novolumes,
|
||||
claimWithClass("non-existing", newClaimArray("claim11-14", "uid11-14", "1Gi", "", v1.ClaimPending)),
|
||||
claimWithClass("non-existing", newClaimArray("claim11-14", "uid11-14", "1Gi", "", v1.ClaimPending)),
|
||||
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// No provisioning with class=""
|
||||
"11-15 - no provisioning with class=''",
|
||||
novolumes,
|
||||
novolumes,
|
||||
claimWithClass("", newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending)),
|
||||
claimWithClass("", newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending)),
|
||||
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// No provisioning with class=nil
|
||||
"11-16 - no provisioning with class=nil",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// No provisioning + normal event with external provisioner
|
||||
"11-17 - external provisioner",
|
||||
novolumes,
|
||||
novolumes,
|
||||
claimWithClass("external", newClaimArray("claim11-17", "uid11-17", "1Gi", "", v1.ClaimPending)),
|
||||
claimWithAnnotation(annStorageProvisioner, "vendor.com/my-volume",
|
||||
claimWithClass("external", newClaimArray("claim11-17", "uid11-17", "1Gi", "", v1.ClaimPending))),
|
||||
[]string{"Normal ExternalProvisioning"},
|
||||
noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// No provisioning + warning event with unknown internal provisioner
|
||||
"11-18 - unknown internal provisioner",
|
||||
novolumes,
|
||||
novolumes,
|
||||
claimWithClass("unknown-internal", newClaimArray("claim11-18", "uid11-18", "1Gi", "", v1.ClaimPending)),
|
||||
claimWithClass("unknown-internal", newClaimArray("claim11-18", "uid11-18", "1Gi", "", v1.ClaimPending)),
|
||||
[]string{"Warning ProvisioningFailed"},
|
||||
noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
|
||||
},
|
||||
}
|
||||
runSyncTests(t, tests, storageClasses)
|
||||
}
|
||||
|
||||
func TestAlphaProvisionSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
{
|
||||
// Provision a volume with alpha annotation
|
||||
"14-1 - successful alpha provisioning",
|
||||
novolumes,
|
||||
newVolumeArray("pvc-uid14-1", "1Gi", "uid14-1", "claim14-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
||||
newClaimArray("claim14-1", "uid14-1", "1Gi", "", v1.ClaimPending, storageutil.AlphaStorageClassAnnotation),
|
||||
// Binding will be completed in the next syncClaim
|
||||
newClaimArray("claim14-1", "uid14-1", "1Gi", "", v1.ClaimPending, storageutil.AlphaStorageClassAnnotation, annStorageProvisioner),
|
||||
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provisionAlphaSuccess}, testSyncClaim),
|
||||
},
|
||||
{
|
||||
// Provision success - there is already a volume available, still
|
||||
// we provision a new one when requested.
|
||||
"14-2 - no alpha provisioning when there is a volume available",
|
||||
newVolumeArray("volume14-2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
[]*v1.PersistentVolume{
|
||||
newVolume("volume14-2", "1Gi", "", "", v1.VolumePending, v1.PersistentVolumeReclaimRetain),
|
||||
newVolume("pvc-uid14-2", "1Gi", "uid14-2", "claim14-2", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
||||
},
|
||||
newClaimArray("claim14-2", "uid14-2", "1Gi", "", v1.ClaimPending, storageutil.AlphaStorageClassAnnotation),
|
||||
// Binding will be completed in the next syncClaim
|
||||
newClaimArray("claim14-2", "uid14-2", "1Gi", "", v1.ClaimPending, storageutil.AlphaStorageClassAnnotation, annStorageProvisioner),
|
||||
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provisionAlphaSuccess}, testSyncClaim),
|
||||
},
|
||||
}
|
||||
runSyncTests(t, tests, []*storage.StorageClass{})
|
||||
}
|
||||
|
||||
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
||||
// volume/claims. The test follows this pattern:
|
||||
// 0. Load the controller with initial data.
|
||||
// 1. Call controllerTest.testCall() once as in TestSync()
|
||||
// 2. For all volumes/claims changed by previous syncVolume/syncClaim calls,
|
||||
// call appropriate syncVolume/syncClaim (simulating "volume/claim changed"
|
||||
// events). Go to 2. if these calls change anything.
|
||||
// 3. When all changes are processed and no new changes were made, call
|
||||
// syncVolume/syncClaim on all volumes/claims (simulating "periodic sync").
|
||||
// 4. If some changes were done by step 3., go to 2. (simulation of
|
||||
// "volume/claim updated" events, eventually performing step 3. again)
|
||||
// 5. When 3. does not do any changes, finish the tests and compare final set
|
||||
// of volumes/claims with expected claims/volumes and report differences.
|
||||
// Some limit of calls in enforced to prevent endless loops.
|
||||
func TestProvisionMultiSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
{
|
||||
// Provision a volume with binding
|
||||
"12-1 - successful provision",
|
||||
novolumes,
|
||||
newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim12-1", "uid12-1", "1Gi", "", v1.ClaimPending, storageutil.StorageClassAnnotation),
|
||||
newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", v1.ClaimBound, storageutil.StorageClassAnnotation, annBoundByController, annBindCompleted, annStorageProvisioner),
|
||||
noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
|
||||
},
|
||||
}
|
||||
|
||||
runMultisyncTests(t, tests, storageClasses, storageClasses[0].Name)
|
||||
}
|
||||
|
||||
// When provisioning is disabled, provisioning a claim should instantly return nil
|
||||
func TestDisablingDynamicProvisioner(t *testing.T) {
|
||||
ctrl := newTestController(nil, nil, nil, nil, false)
|
||||
retVal := ctrl.provisionClaim(nil)
|
||||
if retVal != nil {
|
||||
t.Errorf("Expected nil return but got %v", retVal)
|
||||
}
|
||||
}
|
1539
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/pv_controller.go
generated
vendored
Normal file
1539
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/pv_controller.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
613
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/pv_controller_base.go
generated
vendored
Normal file
613
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/pv_controller_base.go
generated
vendored
Normal file
|
@ -0,0 +1,613 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package persistentvolume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
storage "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
|
||||
"k8s.io/kubernetes/pkg/client/record"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/util/goroutinemap"
|
||||
"k8s.io/kubernetes/pkg/util/workqueue"
|
||||
vol "k8s.io/kubernetes/pkg/volume"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// This file contains the controller base functionality, i.e. framework to
|
||||
// process PV/PVC added/updated/deleted events. The real binding, provisioning,
|
||||
// recycling and deleting is done in pv_controller.go
|
||||
|
||||
// ControllerParameters contains arguments for creation of a new
|
||||
// PersistentVolume controller.
|
||||
type ControllerParameters struct {
|
||||
KubeClient clientset.Interface
|
||||
SyncPeriod time.Duration
|
||||
AlphaProvisioner vol.ProvisionableVolumePlugin
|
||||
VolumePlugins []vol.VolumePlugin
|
||||
Cloud cloudprovider.Interface
|
||||
ClusterName string
|
||||
VolumeSource, ClaimSource, ClassSource cache.ListerWatcher
|
||||
EventRecorder record.EventRecorder
|
||||
EnableDynamicProvisioning bool
|
||||
}
|
||||
|
||||
// NewController creates a new PersistentVolume controller
|
||||
func NewController(p ControllerParameters) *PersistentVolumeController {
|
||||
eventRecorder := p.EventRecorder
|
||||
if eventRecorder == nil {
|
||||
broadcaster := record.NewBroadcaster()
|
||||
broadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: p.KubeClient.Core().Events("")})
|
||||
eventRecorder = broadcaster.NewRecorder(v1.EventSource{Component: "persistentvolume-controller"})
|
||||
}
|
||||
|
||||
controller := &PersistentVolumeController{
|
||||
volumes: newPersistentVolumeOrderedIndex(),
|
||||
claims: cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc),
|
||||
kubeClient: p.KubeClient,
|
||||
eventRecorder: eventRecorder,
|
||||
runningOperations: goroutinemap.NewGoRoutineMap(true /* exponentialBackOffOnError */),
|
||||
cloud: p.Cloud,
|
||||
enableDynamicProvisioning: p.EnableDynamicProvisioning,
|
||||
clusterName: p.ClusterName,
|
||||
createProvisionedPVRetryCount: createProvisionedPVRetryCount,
|
||||
createProvisionedPVInterval: createProvisionedPVInterval,
|
||||
alphaProvisioner: p.AlphaProvisioner,
|
||||
claimQueue: workqueue.NewNamed("claims"),
|
||||
volumeQueue: workqueue.NewNamed("volumes"),
|
||||
}
|
||||
|
||||
controller.volumePluginMgr.InitPlugins(p.VolumePlugins, controller)
|
||||
if controller.alphaProvisioner != nil {
|
||||
if err := controller.alphaProvisioner.Init(controller); err != nil {
|
||||
glog.Errorf("PersistentVolumeController: error initializing alpha provisioner plugin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
volumeSource := p.VolumeSource
|
||||
if volumeSource == nil {
|
||||
volumeSource = &cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
return p.KubeClient.Core().PersistentVolumes().List(options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
return p.KubeClient.Core().PersistentVolumes().Watch(options)
|
||||
},
|
||||
}
|
||||
}
|
||||
controller.volumeSource = volumeSource
|
||||
|
||||
claimSource := p.ClaimSource
|
||||
if claimSource == nil {
|
||||
claimSource = &cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
return p.KubeClient.Core().PersistentVolumeClaims(v1.NamespaceAll).List(options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
return p.KubeClient.Core().PersistentVolumeClaims(v1.NamespaceAll).Watch(options)
|
||||
},
|
||||
}
|
||||
}
|
||||
controller.claimSource = claimSource
|
||||
|
||||
classSource := p.ClassSource
|
||||
if classSource == nil {
|
||||
classSource = &cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
return p.KubeClient.Storage().StorageClasses().List(options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
return p.KubeClient.Storage().StorageClasses().Watch(options)
|
||||
},
|
||||
}
|
||||
}
|
||||
controller.classSource = classSource
|
||||
|
||||
controller.volumeInformer, controller.volumeController = cache.NewIndexerInformer(
|
||||
volumeSource,
|
||||
&v1.PersistentVolume{},
|
||||
p.SyncPeriod,
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
|
||||
UpdateFunc: func(oldObj, newObj interface{}) { controller.enqueueWork(controller.volumeQueue, newObj) },
|
||||
DeleteFunc: func(obj interface{}) { controller.enqueueWork(controller.volumeQueue, obj) },
|
||||
},
|
||||
cache.Indexers{"accessmodes": accessModesIndexFunc},
|
||||
)
|
||||
controller.claimInformer, controller.claimController = cache.NewInformer(
|
||||
claimSource,
|
||||
&v1.PersistentVolumeClaim{},
|
||||
p.SyncPeriod,
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
|
||||
UpdateFunc: func(oldObj, newObj interface{}) { controller.enqueueWork(controller.claimQueue, newObj) },
|
||||
DeleteFunc: func(obj interface{}) { controller.enqueueWork(controller.claimQueue, obj) },
|
||||
},
|
||||
)
|
||||
|
||||
// This is just a cache of StorageClass instances, no special actions are
|
||||
// needed when a class is created/deleted/updated.
|
||||
controller.classes = cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
|
||||
controller.classReflector = cache.NewReflector(
|
||||
classSource,
|
||||
&storage.StorageClass{},
|
||||
controller.classes,
|
||||
p.SyncPeriod,
|
||||
)
|
||||
return controller
|
||||
}
|
||||
|
||||
// initializeCaches fills all controller caches with initial data from etcd in
|
||||
// order to have the caches already filled when first addClaim/addVolume to
|
||||
// perform initial synchronization of the controller.
|
||||
func (ctrl *PersistentVolumeController) initializeCaches(volumeSource, claimSource cache.ListerWatcher) {
|
||||
volumeListObj, err := volumeSource.List(v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Errorf("PersistentVolumeController can't initialize caches: %v", err)
|
||||
return
|
||||
}
|
||||
volumeList, ok := volumeListObj.(*v1.PersistentVolumeList)
|
||||
if !ok {
|
||||
glog.Errorf("PersistentVolumeController can't initialize caches, expected list of volumes, got: %#v", volumeListObj)
|
||||
return
|
||||
}
|
||||
for _, volume := range volumeList.Items {
|
||||
// Ignore template volumes from kubernetes 1.2
|
||||
deleted := ctrl.upgradeVolumeFrom1_2(&volume)
|
||||
if !deleted {
|
||||
clone, err := api.Scheme.DeepCopy(&volume)
|
||||
if err != nil {
|
||||
glog.Errorf("error cloning volume %q: %v", volume.Name, err)
|
||||
continue
|
||||
}
|
||||
volumeClone := clone.(*v1.PersistentVolume)
|
||||
ctrl.storeVolumeUpdate(volumeClone)
|
||||
}
|
||||
}
|
||||
|
||||
claimListObj, err := claimSource.List(v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Errorf("PersistentVolumeController can't initialize caches: %v", err)
|
||||
return
|
||||
}
|
||||
claimList, ok := claimListObj.(*v1.PersistentVolumeClaimList)
|
||||
if !ok {
|
||||
glog.Errorf("PersistentVolumeController can't initialize caches, expected list of claims, got: %#v", claimListObj)
|
||||
return
|
||||
}
|
||||
for _, claim := range claimList.Items {
|
||||
clone, err := api.Scheme.DeepCopy(&claim)
|
||||
if err != nil {
|
||||
glog.Errorf("error cloning claim %q: %v", claimToClaimKey(&claim), err)
|
||||
continue
|
||||
}
|
||||
claimClone := clone.(*v1.PersistentVolumeClaim)
|
||||
ctrl.storeClaimUpdate(claimClone)
|
||||
}
|
||||
glog.V(4).Infof("controller initialized")
|
||||
}
|
||||
|
||||
// enqueueWork adds volume or claim to given work queue.
|
||||
func (ctrl *PersistentVolumeController) enqueueWork(queue workqueue.Interface, obj interface{}) {
|
||||
// Beware of "xxx deleted" events
|
||||
if unknown, ok := obj.(cache.DeletedFinalStateUnknown); ok && unknown.Obj != nil {
|
||||
obj = unknown.Obj
|
||||
}
|
||||
objName, err := controller.KeyFunc(obj)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to get key from object: %v", err)
|
||||
return
|
||||
}
|
||||
glog.V(5).Infof("enqueued %q for sync", objName)
|
||||
queue.Add(objName)
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) storeVolumeUpdate(volume interface{}) (bool, error) {
|
||||
return storeObjectUpdate(ctrl.volumes.store, volume, "volume")
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) storeClaimUpdate(claim interface{}) (bool, error) {
|
||||
return storeObjectUpdate(ctrl.claims, claim, "claim")
|
||||
}
|
||||
|
||||
// updateVolume runs in worker thread and handles "volume added",
|
||||
// "volume updated" and "periodic sync" events.
|
||||
func (ctrl *PersistentVolumeController) updateVolume(volume *v1.PersistentVolume) {
|
||||
if deleted := ctrl.upgradeVolumeFrom1_2(volume); deleted {
|
||||
// volume deleted
|
||||
return
|
||||
}
|
||||
|
||||
// Store the new volume version in the cache and do not process it if this
|
||||
// is an old version.
|
||||
new, err := ctrl.storeVolumeUpdate(volume)
|
||||
if err != nil {
|
||||
glog.Errorf("%v", err)
|
||||
}
|
||||
if !new {
|
||||
return
|
||||
}
|
||||
|
||||
err = ctrl.syncVolume(volume)
|
||||
if err != nil {
|
||||
if errors.IsConflict(err) {
|
||||
// Version conflict error happens quite often and the controller
|
||||
// recovers from it easily.
|
||||
glog.V(3).Infof("could not sync volume %q: %+v", volume.Name, err)
|
||||
} else {
|
||||
glog.Errorf("could not sync volume %q: %+v", volume.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteVolume runs in worker thread and handles "volume deleted" event.
|
||||
func (ctrl *PersistentVolumeController) deleteVolume(volume *v1.PersistentVolume) {
|
||||
_ = ctrl.volumes.store.Delete(volume)
|
||||
glog.V(4).Infof("volume %q deleted", volume.Name)
|
||||
|
||||
if volume.Spec.ClaimRef == nil {
|
||||
return
|
||||
}
|
||||
// sync the claim when its volume is deleted. Explicitly syncing the
|
||||
// claim here in response to volume deletion prevents the claim from
|
||||
// waiting until the next sync period for its Lost status.
|
||||
claimKey := claimrefToClaimKey(volume.Spec.ClaimRef)
|
||||
glog.V(5).Infof("deleteVolume[%s]: scheduling sync of claim %q", volume.Name, claimKey)
|
||||
ctrl.claimQueue.Add(claimKey)
|
||||
}
|
||||
|
||||
// updateClaim runs in worker thread and handles "claim added",
|
||||
// "claim updated" and "periodic sync" events.
|
||||
func (ctrl *PersistentVolumeController) updateClaim(claim *v1.PersistentVolumeClaim) {
|
||||
// Store the new claim version in the cache and do not process it if this is
|
||||
// an old version.
|
||||
new, err := ctrl.storeClaimUpdate(claim)
|
||||
if err != nil {
|
||||
glog.Errorf("%v", err)
|
||||
}
|
||||
if !new {
|
||||
return
|
||||
}
|
||||
err = ctrl.syncClaim(claim)
|
||||
if err != nil {
|
||||
if errors.IsConflict(err) {
|
||||
// Version conflict error happens quite often and the controller
|
||||
// recovers from it easily.
|
||||
glog.V(3).Infof("could not sync claim %q: %+v", claimToClaimKey(claim), err)
|
||||
} else {
|
||||
glog.Errorf("could not sync volume %q: %+v", claimToClaimKey(claim), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteClaim runs in worker thread and handles "claim deleted" event.
|
||||
func (ctrl *PersistentVolumeController) deleteClaim(claim *v1.PersistentVolumeClaim) {
|
||||
_ = ctrl.claims.Delete(claim)
|
||||
glog.V(4).Infof("claim %q deleted", claimToClaimKey(claim))
|
||||
|
||||
// sync the volume when its claim is deleted. Explicitly sync'ing the
|
||||
// volume here in response to claim deletion prevents the volume from
|
||||
// waiting until the next sync period for its Release.
|
||||
volumeName := claim.Spec.VolumeName
|
||||
glog.V(5).Infof("deleteClaim[%s]: scheduling sync of volume %q", claimToClaimKey(claim), volumeName)
|
||||
ctrl.volumeQueue.Add(volumeName)
|
||||
}
|
||||
|
||||
// Run starts all of this controller's control loops
|
||||
func (ctrl *PersistentVolumeController) Run(stopCh <-chan struct{}) {
|
||||
glog.V(1).Infof("starting PersistentVolumeController")
|
||||
ctrl.initializeCaches(ctrl.volumeSource, ctrl.claimSource)
|
||||
go ctrl.volumeController.Run(stopCh)
|
||||
go ctrl.claimController.Run(stopCh)
|
||||
go ctrl.classReflector.RunUntil(stopCh)
|
||||
go wait.Until(ctrl.volumeWorker, time.Second, stopCh)
|
||||
go wait.Until(ctrl.claimWorker, time.Second, stopCh)
|
||||
|
||||
<-stopCh
|
||||
|
||||
ctrl.claimQueue.ShutDown()
|
||||
ctrl.volumeQueue.ShutDown()
|
||||
}
|
||||
|
||||
// volumeWorker processes items from volumeQueue. It must run only once,
|
||||
// syncVolume is not assured to be reentrant.
|
||||
func (ctrl *PersistentVolumeController) volumeWorker() {
|
||||
workFunc := func() bool {
|
||||
keyObj, quit := ctrl.volumeQueue.Get()
|
||||
if quit {
|
||||
return true
|
||||
}
|
||||
defer ctrl.volumeQueue.Done(keyObj)
|
||||
key := keyObj.(string)
|
||||
glog.V(5).Infof("volumeWorker[%s]", key)
|
||||
|
||||
volumeObj, found, err := ctrl.volumeInformer.GetByKey(key)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("error getting volume %q from informer: %v", key, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if found {
|
||||
// The volume still exists in informer cache, the event must have
|
||||
// been add/update/sync
|
||||
volume, ok := volumeObj.(*v1.PersistentVolume)
|
||||
if !ok {
|
||||
glog.Errorf("expected volume, got %+v", volumeObj)
|
||||
return false
|
||||
}
|
||||
ctrl.updateVolume(volume)
|
||||
return false
|
||||
}
|
||||
|
||||
// The volume is not in informer cache, the event must have been
|
||||
// "delete"
|
||||
volumeObj, found, err = ctrl.volumes.store.GetByKey(key)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("error getting volume %q from cache: %v", key, err)
|
||||
return false
|
||||
}
|
||||
if !found {
|
||||
// The controller has already processed the delete event and
|
||||
// deleted the volume from its cache
|
||||
glog.V(2).Infof("deletion of volume %q was already processed", key)
|
||||
return false
|
||||
}
|
||||
volume, ok := volumeObj.(*v1.PersistentVolume)
|
||||
if !ok {
|
||||
glog.Errorf("expected volume, got %+v", volumeObj)
|
||||
return false
|
||||
}
|
||||
ctrl.deleteVolume(volume)
|
||||
return false
|
||||
}
|
||||
for {
|
||||
if quit := workFunc(); quit {
|
||||
glog.Infof("volume worker queue shutting down")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// claimWorker processes items from claimQueue. It must run only once,
|
||||
// syncClaim is not reentrant.
|
||||
func (ctrl *PersistentVolumeController) claimWorker() {
|
||||
workFunc := func() bool {
|
||||
keyObj, quit := ctrl.claimQueue.Get()
|
||||
if quit {
|
||||
return true
|
||||
}
|
||||
defer ctrl.claimQueue.Done(keyObj)
|
||||
key := keyObj.(string)
|
||||
glog.V(5).Infof("claimWorker[%s]", key)
|
||||
|
||||
claimObj, found, err := ctrl.claimInformer.GetByKey(key)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("error getting claim %q from informer: %v", key, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if found {
|
||||
// The claim still exists in informer cache, the event must have
|
||||
// been add/update/sync
|
||||
claim, ok := claimObj.(*v1.PersistentVolumeClaim)
|
||||
if !ok {
|
||||
glog.Errorf("expected claim, got %+v", claimObj)
|
||||
return false
|
||||
}
|
||||
ctrl.updateClaim(claim)
|
||||
return false
|
||||
}
|
||||
|
||||
// The claim is not in informer cache, the event must have been "delete"
|
||||
claimObj, found, err = ctrl.claims.GetByKey(key)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("error getting claim %q from cache: %v", key, err)
|
||||
return false
|
||||
}
|
||||
if !found {
|
||||
// The controller has already processed the delete event and
|
||||
// deleted the claim from its cache
|
||||
glog.V(2).Infof("deletion of claim %q was already processed", key)
|
||||
return false
|
||||
}
|
||||
claim, ok := claimObj.(*v1.PersistentVolumeClaim)
|
||||
if !ok {
|
||||
glog.Errorf("expected claim, got %+v", claimObj)
|
||||
return false
|
||||
}
|
||||
ctrl.deleteClaim(claim)
|
||||
return false
|
||||
}
|
||||
for {
|
||||
if quit := workFunc(); quit {
|
||||
glog.Infof("claim worker queue shutting down")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// these pair of constants are used by the provisioner in Kubernetes 1.2.
|
||||
pvProvisioningRequiredAnnotationKey = "volume.experimental.kubernetes.io/provisioning-required"
|
||||
pvProvisioningCompletedAnnotationValue = "volume.experimental.kubernetes.io/provisioning-completed"
|
||||
)
|
||||
|
||||
// upgradeVolumeFrom1_2 updates PV from Kubernetes 1.2 to 1.3 and newer. In 1.2,
|
||||
// we used template PersistentVolume instances for dynamic provisioning. In 1.3
|
||||
// and later, these template (and not provisioned) instances must be removed to
|
||||
// make the controller to provision a new PV.
|
||||
// It returns true if the volume was deleted.
|
||||
// TODO: remove this function when upgrade from 1.2 becomes unsupported.
|
||||
func (ctrl *PersistentVolumeController) upgradeVolumeFrom1_2(volume *v1.PersistentVolume) bool {
|
||||
annValue, found := volume.Annotations[pvProvisioningRequiredAnnotationKey]
|
||||
if !found {
|
||||
// The volume is not template
|
||||
return false
|
||||
}
|
||||
if annValue == pvProvisioningCompletedAnnotationValue {
|
||||
// The volume is already fully provisioned. The new controller will
|
||||
// ignore this annotation and it will obey its ReclaimPolicy, which is
|
||||
// likely to delete the volume when appropriate claim is deleted.
|
||||
return false
|
||||
}
|
||||
glog.V(2).Infof("deleting unprovisioned template volume %q from Kubernetes 1.2.", volume.Name)
|
||||
err := ctrl.kubeClient.Core().PersistentVolumes().Delete(volume.Name, nil)
|
||||
if err != nil {
|
||||
glog.Errorf("cannot delete unprovisioned template volume %q: %v", volume.Name, err)
|
||||
}
|
||||
// Remove from local cache
|
||||
err = ctrl.volumes.store.Delete(volume)
|
||||
if err != nil {
|
||||
glog.Errorf("cannot remove volume %q from local cache: %v", volume.Name, err)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// setClaimProvisioner saves
|
||||
// claim.Annotations[annStorageProvisioner] = class.Provisioner
|
||||
func (ctrl *PersistentVolumeController) setClaimProvisioner(claim *v1.PersistentVolumeClaim, class *storage.StorageClass) (*v1.PersistentVolumeClaim, error) {
|
||||
if val, ok := claim.Annotations[annDynamicallyProvisioned]; ok && val == class.Provisioner {
|
||||
// annotation is already set, nothing to do
|
||||
return claim, nil
|
||||
}
|
||||
|
||||
// The volume from method args can be pointing to watcher cache. We must not
|
||||
// modify these, therefore create a copy.
|
||||
clone, err := api.Scheme.DeepCopy(claim)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error cloning pv: %v", err)
|
||||
}
|
||||
claimClone, ok := clone.(*v1.PersistentVolumeClaim)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unexpected claim cast error : %v", claimClone)
|
||||
}
|
||||
v1.SetMetaDataAnnotation(&claimClone.ObjectMeta, annStorageProvisioner, class.Provisioner)
|
||||
newClaim, err := ctrl.kubeClient.Core().PersistentVolumeClaims(claim.Namespace).Update(claimClone)
|
||||
if err != nil {
|
||||
return newClaim, err
|
||||
}
|
||||
_, err = ctrl.storeClaimUpdate(newClaim)
|
||||
if err != nil {
|
||||
return newClaim, err
|
||||
}
|
||||
return newClaim, nil
|
||||
}
|
||||
|
||||
// Stateless functions
|
||||
|
||||
func getClaimStatusForLogging(claim *v1.PersistentVolumeClaim) string {
|
||||
bound := v1.HasAnnotation(claim.ObjectMeta, annBindCompleted)
|
||||
boundByController := v1.HasAnnotation(claim.ObjectMeta, annBoundByController)
|
||||
|
||||
return fmt.Sprintf("phase: %s, bound to: %q, bindCompleted: %v, boundByController: %v", claim.Status.Phase, claim.Spec.VolumeName, bound, boundByController)
|
||||
}
|
||||
|
||||
func getVolumeStatusForLogging(volume *v1.PersistentVolume) string {
|
||||
boundByController := v1.HasAnnotation(volume.ObjectMeta, annBoundByController)
|
||||
claimName := ""
|
||||
if volume.Spec.ClaimRef != nil {
|
||||
claimName = fmt.Sprintf("%s/%s (uid: %s)", volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name, volume.Spec.ClaimRef.UID)
|
||||
}
|
||||
return fmt.Sprintf("phase: %s, bound to: %q, boundByController: %v", volume.Status.Phase, claimName, boundByController)
|
||||
}
|
||||
|
||||
// isVolumeBoundToClaim returns true, if given volume is pre-bound or bound
|
||||
// to specific claim. Both claim.Name and claim.Namespace must be equal.
|
||||
// If claim.UID is present in volume.Spec.ClaimRef, it must be equal too.
|
||||
func isVolumeBoundToClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) bool {
|
||||
if volume.Spec.ClaimRef == nil {
|
||||
return false
|
||||
}
|
||||
if claim.Name != volume.Spec.ClaimRef.Name || claim.Namespace != volume.Spec.ClaimRef.Namespace {
|
||||
return false
|
||||
}
|
||||
if volume.Spec.ClaimRef.UID != "" && claim.UID != volume.Spec.ClaimRef.UID {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// storeObjectUpdate updates given cache with a new object version from Informer
|
||||
// callback (i.e. with events from etcd) or with an object modified by the
|
||||
// controller itself. Returns "true", if the cache was updated, false if the
|
||||
// object is an old version and should be ignored.
|
||||
func storeObjectUpdate(store cache.Store, obj interface{}, className string) (bool, error) {
|
||||
objName, err := controller.KeyFunc(obj)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Couldn't get key for object %+v: %v", obj, err)
|
||||
}
|
||||
oldObj, found, err := store.Get(obj)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error finding %s %q in controller cache: %v", className, objName, err)
|
||||
}
|
||||
|
||||
objAccessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
// This is a new object
|
||||
glog.V(4).Infof("storeObjectUpdate: adding %s %q, version %s", className, objName, objAccessor.GetResourceVersion())
|
||||
if err = store.Add(obj); err != nil {
|
||||
return false, fmt.Errorf("Error adding %s %q to controller cache: %v", className, objName, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
oldObjAccessor, err := meta.Accessor(oldObj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
objResourceVersion, err := strconv.ParseInt(objAccessor.GetResourceVersion(), 10, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error parsing ResourceVersion %q of %s %q: %s", objAccessor.GetResourceVersion(), className, objName, err)
|
||||
}
|
||||
oldObjResourceVersion, err := strconv.ParseInt(oldObjAccessor.GetResourceVersion(), 10, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error parsing old ResourceVersion %q of %s %q: %s", oldObjAccessor.GetResourceVersion(), className, objName, err)
|
||||
}
|
||||
|
||||
// Throw away only older version, let the same version pass - we do want to
|
||||
// get periodic sync events.
|
||||
if oldObjResourceVersion > objResourceVersion {
|
||||
glog.V(4).Infof("storeObjectUpdate: ignoring %s %q version %s", className, objName, objAccessor.GetResourceVersion())
|
||||
return false, nil
|
||||
}
|
||||
|
||||
glog.V(4).Infof("storeObjectUpdate updating %s %q with version %s", className, objName, objAccessor.GetResourceVersion())
|
||||
if err = store.Update(obj); err != nil {
|
||||
return false, fmt.Errorf("Error updating %s %q in controller cache: %v", className, objName, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
285
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/pv_controller_test.go
generated
vendored
Normal file
285
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/pv_controller_test.go
generated
vendored
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package persistentvolume
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
|
||||
fcache "k8s.io/kubernetes/pkg/client/testing/cache"
|
||||
)
|
||||
|
||||
// Test the real controller methods (add/update/delete claim/volume) with
|
||||
// a fake API server.
|
||||
// There is no controller API to 'initiate syncAll now', therefore these tests
|
||||
// can't reliably simulate periodic sync of volumes/claims - it would be
|
||||
// either very timing-sensitive or slow to wait for real periodic sync.
|
||||
func TestControllerSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
// [Unit test set 5] - controller tests.
|
||||
// We test the controller as if
|
||||
// it was connected to real API server, i.e. we call add/update/delete
|
||||
// Claim/Volume methods. Also, all changes to volumes and claims are
|
||||
// sent to add/update/delete Claim/Volume as real controller would do.
|
||||
{
|
||||
// addClaim gets a new claim. Check it's bound to a volume.
|
||||
"5-2 - complete bind",
|
||||
newVolumeArray("volume5-2", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain),
|
||||
newVolumeArray("volume5-2", "1Gi", "uid5-2", "claim5-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
noclaims, /* added in testAddClaim5_2 */
|
||||
newClaimArray("claim5-2", "uid5-2", "1Gi", "volume5-2", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noevents, noerrors,
|
||||
// Custom test function that generates an add event
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
claim := newClaim("claim5-2", "uid5-2", "1Gi", "", v1.ClaimPending)
|
||||
reactor.addClaimEvent(claim)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// deleteClaim with a bound claim makes bound volume released.
|
||||
"5-3 - delete claim",
|
||||
newVolumeArray("volume5-3", "10Gi", "uid5-3", "claim5-3", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newVolumeArray("volume5-3", "10Gi", "uid5-3", "claim5-3", v1.VolumeReleased, v1.PersistentVolumeReclaimRetain, annBoundByController),
|
||||
newClaimArray("claim5-3", "uid5-3", "1Gi", "volume5-3", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
// Custom test function that generates a delete event
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
obj := ctrl.claims.List()[0]
|
||||
claim := obj.(*v1.PersistentVolumeClaim)
|
||||
reactor.deleteClaimEvent(claim)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// deleteVolume with a bound volume. Check the claim is Lost.
|
||||
"5-4 - delete volume",
|
||||
newVolumeArray("volume5-4", "1Gi", "uid5-4", "claim5-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain),
|
||||
novolumes,
|
||||
newClaimArray("claim5-4", "uid5-4", "1Gi", "volume5-4", v1.ClaimBound, annBoundByController, annBindCompleted),
|
||||
newClaimArray("claim5-4", "uid5-4", "1Gi", "volume5-4", v1.ClaimLost, annBoundByController, annBindCompleted),
|
||||
[]string{"Warning ClaimLost"}, noerrors,
|
||||
// Custom test function that generates a delete event
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
obj := ctrl.volumes.store.List()[0]
|
||||
volume := obj.(*v1.PersistentVolume)
|
||||
reactor.deleteVolumeEvent(volume)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// addVolume with provisioned volume from Kubernetes 1.2. No "action"
|
||||
// is expected - it should stay bound.
|
||||
"5-5 - add bound volume from 1.2",
|
||||
novolumes,
|
||||
[]*v1.PersistentVolume{addVolumeAnnotation(newVolume("volume5-5", "1Gi", "uid5-5", "claim5-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete), pvProvisioningRequiredAnnotationKey, pvProvisioningCompletedAnnotationValue)},
|
||||
newClaimArray("claim5-5", "uid5-5", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim5-5", "uid5-5", "1Gi", "volume5-5", v1.ClaimBound, annBindCompleted, annBoundByController),
|
||||
noevents, noerrors,
|
||||
// Custom test function that generates a add event
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
volume := newVolume("volume5-5", "1Gi", "uid5-5", "claim5-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete)
|
||||
volume = addVolumeAnnotation(volume, pvProvisioningRequiredAnnotationKey, pvProvisioningCompletedAnnotationValue)
|
||||
reactor.addVolumeEvent(volume)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// updateVolume with provisioned volume from Kubernetes 1.2. No
|
||||
// "action" is expected - it should stay bound.
|
||||
"5-6 - update bound volume from 1.2",
|
||||
[]*v1.PersistentVolume{addVolumeAnnotation(newVolume("volume5-6", "1Gi", "uid5-6", "claim5-6", v1.VolumeBound, v1.PersistentVolumeReclaimDelete), pvProvisioningRequiredAnnotationKey, pvProvisioningCompletedAnnotationValue)},
|
||||
[]*v1.PersistentVolume{addVolumeAnnotation(newVolume("volume5-6", "1Gi", "uid5-6", "claim5-6", v1.VolumeBound, v1.PersistentVolumeReclaimDelete), pvProvisioningRequiredAnnotationKey, pvProvisioningCompletedAnnotationValue)},
|
||||
newClaimArray("claim5-6", "uid5-6", "1Gi", "volume5-6", v1.ClaimBound),
|
||||
newClaimArray("claim5-6", "uid5-6", "1Gi", "volume5-6", v1.ClaimBound, annBindCompleted),
|
||||
noevents, noerrors,
|
||||
// Custom test function that generates a add event
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
volume := newVolume("volume5-6", "1Gi", "uid5-6", "claim5-6", v1.VolumeBound, v1.PersistentVolumeReclaimDelete)
|
||||
volume = addVolumeAnnotation(volume, pvProvisioningRequiredAnnotationKey, pvProvisioningCompletedAnnotationValue)
|
||||
reactor.modifyVolumeEvent(volume)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// addVolume with unprovisioned volume from Kubernetes 1.2. The
|
||||
// volume should be deleted.
|
||||
"5-7 - add unprovisioned volume from 1.2",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim5-7", "uid5-7", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim5-7", "uid5-7", "1Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors,
|
||||
// Custom test function that generates a add event
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
volume := newVolume("volume5-7", "1Gi", "uid5-7", "claim5-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete)
|
||||
volume = addVolumeAnnotation(volume, pvProvisioningRequiredAnnotationKey, "yes")
|
||||
reactor.addVolumeEvent(volume)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// updateVolume with unprovisioned volume from Kubernetes 1.2. The
|
||||
// volume should be deleted.
|
||||
"5-8 - update bound volume from 1.2",
|
||||
novolumes,
|
||||
novolumes,
|
||||
newClaimArray("claim5-8", "uid5-8", "1Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim5-8", "uid5-8", "1Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors,
|
||||
// Custom test function that generates a add event
|
||||
func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error {
|
||||
volume := newVolume("volume5-8", "1Gi", "uid5-8", "claim5-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete)
|
||||
volume = addVolumeAnnotation(volume, pvProvisioningRequiredAnnotationKey, "yes")
|
||||
reactor.modifyVolumeEvent(volume)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
glog.V(4).Infof("starting test %q", test.name)
|
||||
|
||||
// Initialize the controller
|
||||
client := &fake.Clientset{}
|
||||
volumeSource := fcache.NewFakePVControllerSource()
|
||||
claimSource := fcache.NewFakePVCControllerSource()
|
||||
ctrl := newTestController(client, volumeSource, claimSource, nil, true)
|
||||
reactor := newVolumeReactor(client, ctrl, volumeSource, claimSource, test.errors)
|
||||
for _, claim := range test.initialClaims {
|
||||
claimSource.Add(claim)
|
||||
reactor.claims[claim.Name] = claim
|
||||
}
|
||||
for _, volume := range test.initialVolumes {
|
||||
volumeSource.Add(volume)
|
||||
reactor.volumes[volume.Name] = volume
|
||||
}
|
||||
|
||||
// Start the controller
|
||||
stopCh := make(chan struct{})
|
||||
go ctrl.Run(stopCh)
|
||||
|
||||
// Wait for the controller to pass initial sync and fill its caches.
|
||||
for !ctrl.volumeController.HasSynced() ||
|
||||
!ctrl.claimController.HasSynced() ||
|
||||
len(ctrl.claims.ListKeys()) < len(test.initialClaims) ||
|
||||
len(ctrl.volumes.store.ListKeys()) < len(test.initialVolumes) {
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
glog.V(4).Infof("controller synced, starting test")
|
||||
|
||||
// Call the tested function
|
||||
err := test.test(ctrl, reactor, test)
|
||||
if err != nil {
|
||||
t.Errorf("Test %q initial test call failed: %v", test.name, err)
|
||||
}
|
||||
// Simulate a periodic resync, just in case some events arrived in a
|
||||
// wrong order.
|
||||
ctrl.claims.Resync()
|
||||
ctrl.volumes.store.Resync()
|
||||
|
||||
err = reactor.waitTest(test)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to run test %s: %v", test.name, err)
|
||||
}
|
||||
close(stopCh)
|
||||
|
||||
evaluateTestResults(ctrl, reactor, test, t)
|
||||
}
|
||||
}
|
||||
|
||||
func storeVersion(t *testing.T, prefix string, c cache.Store, version string, expectedReturn bool) {
|
||||
pv := newVolume("pvName", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete)
|
||||
pv.ResourceVersion = version
|
||||
ret, err := storeObjectUpdate(c, pv, "volume")
|
||||
if err != nil {
|
||||
t.Errorf("%s: expected storeObjectUpdate to succeed, got: %v", prefix, err)
|
||||
}
|
||||
if expectedReturn != ret {
|
||||
t.Errorf("%s: expected storeObjectUpdate to return %v, got: %v", prefix, expectedReturn, ret)
|
||||
}
|
||||
|
||||
// find the stored version
|
||||
|
||||
pvObj, found, err := c.GetByKey("pvName")
|
||||
if err != nil {
|
||||
t.Errorf("expected volume 'pvName' in the cache, got error instead: %v", err)
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected volume 'pvName' in the cache but it was not found")
|
||||
}
|
||||
pv, ok := pvObj.(*v1.PersistentVolume)
|
||||
if !ok {
|
||||
t.Errorf("expected volume in the cache, got different object instead: %#v", pvObj)
|
||||
}
|
||||
|
||||
if ret {
|
||||
if pv.ResourceVersion != version {
|
||||
t.Errorf("expected volume with version %s in the cache, got %s instead", version, pv.ResourceVersion)
|
||||
}
|
||||
} else {
|
||||
if pv.ResourceVersion == version {
|
||||
t.Errorf("expected volume with version other than %s in the cache, got %s instead", version, pv.ResourceVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestControllerCache tests func storeObjectUpdate()
|
||||
func TestControllerCache(t *testing.T) {
|
||||
// Cache under test
|
||||
c := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
|
||||
|
||||
// Store new PV
|
||||
storeVersion(t, "Step1", c, "1", true)
|
||||
// Store the same PV
|
||||
storeVersion(t, "Step2", c, "1", true)
|
||||
// Store newer PV
|
||||
storeVersion(t, "Step3", c, "2", true)
|
||||
// Store older PV - simulating old "PV updated" event or periodic sync with
|
||||
// old data
|
||||
storeVersion(t, "Step4", c, "1", false)
|
||||
// Store newer PV - test integer parsing ("2" > "10" as string,
|
||||
// while 2 < 10 as integers)
|
||||
storeVersion(t, "Step5", c, "10", true)
|
||||
}
|
||||
|
||||
func TestControllerCacheParsingError(t *testing.T) {
|
||||
c := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
|
||||
// There must be something in the cache to compare with
|
||||
storeVersion(t, "Step1", c, "1", true)
|
||||
|
||||
pv := newVolume("pvName", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete)
|
||||
pv.ResourceVersion = "xxx"
|
||||
_, err := storeObjectUpdate(c, pv, "volume")
|
||||
if err == nil {
|
||||
t.Errorf("Expected parsing error, got nil instead")
|
||||
}
|
||||
}
|
||||
|
||||
func addVolumeAnnotation(volume *v1.PersistentVolume, annName, annValue string) *v1.PersistentVolume {
|
||||
if volume.Annotations == nil {
|
||||
volume.Annotations = make(map[string]string)
|
||||
}
|
||||
volume.Annotations[annName] = annValue
|
||||
return volume
|
||||
}
|
197
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/recycle_test.go
generated
vendored
Normal file
197
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/recycle_test.go
generated
vendored
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package persistentvolume
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
storage "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
|
||||
)
|
||||
|
||||
// Test single call to syncVolume, expecting recycling to happen.
|
||||
// 1. Fill in the controller with initial data
|
||||
// 2. Call the syncVolume *once*.
|
||||
// 3. Compare resulting volumes with expected volumes.
|
||||
func TestRecycleSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
{
|
||||
// recycle volume bound by controller
|
||||
"6-1 - successful recycle",
|
||||
newVolumeArray("volume6-1", "1Gi", "uid6-1", "claim6-1", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, annBoundByController),
|
||||
newVolumeArray("volume6-1", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle),
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
// Inject recycler into the controller and call syncVolume. The
|
||||
// recycler simulates one recycle() call that succeeds.
|
||||
wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// recycle volume bound by user
|
||||
"6-2 - successful recycle with prebound volume",
|
||||
newVolumeArray("volume6-2", "1Gi", "uid6-2", "claim6-2", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle),
|
||||
newVolumeArray("volume6-2", "1Gi", "", "claim6-2", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle),
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
// Inject recycler into the controller and call syncVolume. The
|
||||
// recycler simulates one recycle() call that succeeds.
|
||||
wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// recycle failure - plugin not found
|
||||
"6-3 - plugin not found",
|
||||
newVolumeArray("volume6-3", "1Gi", "uid6-3", "claim6-3", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle),
|
||||
withMessage("No recycler plugin found for the volume!", newVolumeArray("volume6-3", "1Gi", "uid6-3", "claim6-3", v1.VolumeFailed, v1.PersistentVolumeReclaimRecycle)),
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeFailedRecycle"}, noerrors, testSyncVolume,
|
||||
},
|
||||
{
|
||||
// recycle failure - newRecycler returns error
|
||||
"6-4 - newRecycler returns error",
|
||||
newVolumeArray("volume6-4", "1Gi", "uid6-4", "claim6-4", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle),
|
||||
withMessage("Failed to create recycler: Mock plugin error: no recycleCalls configured", newVolumeArray("volume6-4", "1Gi", "uid6-4", "claim6-4", v1.VolumeFailed, v1.PersistentVolumeReclaimRecycle)),
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
||||
wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// recycle failure - recycle returns error
|
||||
"6-5 - recycle returns error",
|
||||
newVolumeArray("volume6-5", "1Gi", "uid6-5", "claim6-5", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle),
|
||||
withMessage("Recycler failed: Mock recycle error", newVolumeArray("volume6-5", "1Gi", "uid6-5", "claim6-5", v1.VolumeFailed, v1.PersistentVolumeReclaimRecycle)),
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
||||
wrapTestWithReclaimCalls(operationRecycle, []error{errors.New("Mock recycle error")}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// recycle success(?) - volume is deleted before doRecycle() starts
|
||||
"6-6 - volume is deleted before recycling",
|
||||
newVolumeArray("volume6-6", "1Gi", "uid6-6", "claim6-6", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle),
|
||||
novolumes,
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||
// Delete the volume before recycle operation starts
|
||||
reactor.lock.Lock()
|
||||
delete(reactor.volumes, "volume6-6")
|
||||
reactor.lock.Unlock()
|
||||
}),
|
||||
},
|
||||
{
|
||||
// recycle success(?) - volume is recycled by previous recycler just
|
||||
// at the time new doRecycle() starts. This simulates "volume no
|
||||
// longer needs recycling, skipping".
|
||||
"6-7 - volume is deleted before recycling",
|
||||
newVolumeArray("volume6-7", "1Gi", "uid6-7", "claim6-7", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, annBoundByController),
|
||||
newVolumeArray("volume6-7", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle),
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||
// Mark the volume as Available before the recycler starts
|
||||
reactor.lock.Lock()
|
||||
volume := reactor.volumes["volume6-7"]
|
||||
volume.Spec.ClaimRef = nil
|
||||
volume.Status.Phase = v1.VolumeAvailable
|
||||
volume.Annotations = nil
|
||||
reactor.lock.Unlock()
|
||||
}),
|
||||
},
|
||||
{
|
||||
// recycle success(?) - volume bound by user is recycled by previous
|
||||
// recycler just at the time new doRecycle() starts. This simulates
|
||||
// "volume no longer needs recycling, skipping" with volume bound by
|
||||
// user.
|
||||
"6-8 - prebound volume is deleted before recycling",
|
||||
newVolumeArray("volume6-8", "1Gi", "uid6-8", "claim6-8", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle),
|
||||
newVolumeArray("volume6-8", "1Gi", "", "claim6-8", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle),
|
||||
noclaims,
|
||||
noclaims,
|
||||
noevents, noerrors,
|
||||
wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
|
||||
// Mark the volume as Available before the recycler starts
|
||||
reactor.lock.Lock()
|
||||
volume := reactor.volumes["volume6-8"]
|
||||
volume.Spec.ClaimRef.UID = ""
|
||||
volume.Status.Phase = v1.VolumeAvailable
|
||||
reactor.lock.Unlock()
|
||||
}),
|
||||
},
|
||||
{
|
||||
// recycle success - volume bound by user is recycled, while a new
|
||||
// claim is created with another UID.
|
||||
"6-9 - prebound volume is recycled while the claim exists",
|
||||
newVolumeArray("volume6-9", "1Gi", "uid6-9", "claim6-9", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle),
|
||||
newVolumeArray("volume6-9", "1Gi", "", "claim6-9", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle),
|
||||
newClaimArray("claim6-9", "uid6-9-x", "10Gi", "", v1.ClaimPending),
|
||||
newClaimArray("claim6-9", "uid6-9-x", "10Gi", "", v1.ClaimPending),
|
||||
noevents, noerrors,
|
||||
// Inject recycler into the controller and call syncVolume. The
|
||||
// recycler simulates one recycle() call that succeeds.
|
||||
wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
|
||||
},
|
||||
{
|
||||
// volume has unknown reclaim policy - failure expected
|
||||
"6-10 - unknown reclaim policy",
|
||||
newVolumeArray("volume6-10", "1Gi", "uid6-10", "claim6-10", v1.VolumeBound, "Unknown"),
|
||||
withMessage("Volume has unrecognized PersistentVolumeReclaimPolicy", newVolumeArray("volume6-10", "1Gi", "uid6-10", "claim6-10", v1.VolumeFailed, "Unknown")),
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeUnknownReclaimPolicy"}, noerrors, testSyncVolume,
|
||||
},
|
||||
}
|
||||
runSyncTests(t, tests, []*storage.StorageClass{})
|
||||
}
|
||||
|
||||
// Test multiple calls to syncClaim/syncVolume and periodic sync of all
|
||||
// volume/claims. The test follows this pattern:
|
||||
// 0. Load the controller with initial data.
|
||||
// 1. Call controllerTest.testCall() once as in TestSync()
|
||||
// 2. For all volumes/claims changed by previous syncVolume/syncClaim calls,
|
||||
// call appropriate syncVolume/syncClaim (simulating "volume/claim changed"
|
||||
// events). Go to 2. if these calls change anything.
|
||||
// 3. When all changes are processed and no new changes were made, call
|
||||
// syncVolume/syncClaim on all volumes/claims (simulating "periodic sync").
|
||||
// 4. If some changes were done by step 3., go to 2. (simulation of
|
||||
// "volume/claim updated" events, eventually performing step 3. again)
|
||||
// 5. When 3. does not do any changes, finish the tests and compare final set
|
||||
// of volumes/claims with expected claims/volumes and report differences.
|
||||
// Some limit of calls in enforced to prevent endless loops.
|
||||
func TestRecycleMultiSync(t *testing.T) {
|
||||
tests := []controllerTest{
|
||||
{
|
||||
// recycle failure - recycle returns error. The controller should
|
||||
// try again.
|
||||
"7-1 - recycle returns error",
|
||||
newVolumeArray("volume7-1", "1Gi", "uid7-1", "claim7-1", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle),
|
||||
newVolumeArray("volume7-1", "1Gi", "", "claim7-1", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle),
|
||||
noclaims,
|
||||
noclaims,
|
||||
[]string{"Warning VolumeFailedRecycle"}, noerrors,
|
||||
wrapTestWithReclaimCalls(operationRecycle, []error{errors.New("Mock recycle error"), nil}, testSyncVolume),
|
||||
},
|
||||
}
|
||||
|
||||
runMultisyncTests(t, tests, []*storage.StorageClass{}, "")
|
||||
}
|
82
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/volume_host.go
generated
vendored
Normal file
82
vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/volume_host.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package persistentvolume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
"k8s.io/kubernetes/pkg/util/io"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
vol "k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
// VolumeHost interface implementation for PersistentVolumeController.
|
||||
|
||||
var _ vol.VolumeHost = &PersistentVolumeController{}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetPluginDir(pluginName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetKubeClient() clientset.Interface {
|
||||
return ctrl.kubeClient
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) NewWrapperMounter(volName string, spec vol.Spec, pod *v1.Pod, opts vol.VolumeOptions) (vol.Mounter, error) {
|
||||
return nil, fmt.Errorf("PersistentVolumeController.NewWrapperMounter is not implemented")
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) NewWrapperUnmounter(volName string, spec vol.Spec, podUID types.UID) (vol.Unmounter, error) {
|
||||
return nil, fmt.Errorf("PersistentVolumeController.NewWrapperMounter is not implemented")
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetCloudProvider() cloudprovider.Interface {
|
||||
return ctrl.cloud
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetMounter() mount.Interface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetWriter() io.Writer {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetHostName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetHostIP() (net.IP, error) {
|
||||
return nil, fmt.Errorf("PersistentVolumeController.GetHostIP() is not implemented")
|
||||
}
|
||||
|
||||
func (ctrl *PersistentVolumeController) GetNodeAllocatable() (v1.ResourceList, error) {
|
||||
return v1.ResourceList{}, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue