mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 01:06:27 +00:00
provisioning: Unit tests
This commit is contained in:
parent
514d595881
commit
9fb0f7a3fd
@ -41,7 +41,6 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/conversion"
|
"k8s.io/kubernetes/pkg/conversion"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
"k8s.io/kubernetes/pkg/types"
|
"k8s.io/kubernetes/pkg/types"
|
||||||
"k8s.io/kubernetes/pkg/util"
|
|
||||||
"k8s.io/kubernetes/pkg/util/diff"
|
"k8s.io/kubernetes/pkg/util/diff"
|
||||||
vol "k8s.io/kubernetes/pkg/volume"
|
vol "k8s.io/kubernetes/pkg/volume"
|
||||||
)
|
)
|
||||||
@ -91,6 +90,7 @@ type controllerTest struct {
|
|||||||
type testCall func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error
|
type testCall func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error
|
||||||
|
|
||||||
const testNamespace = "default"
|
const testNamespace = "default"
|
||||||
|
const mockPluginName = "MockVolumePlugin"
|
||||||
|
|
||||||
var versionConflictError = errors.New("VersionError")
|
var versionConflictError = errors.New("VersionError")
|
||||||
var novolumes []*api.PersistentVolume
|
var novolumes []*api.PersistentVolume
|
||||||
@ -135,6 +135,26 @@ func (r *volumeReactor) React(action core.Action) (handled bool, ret runtime.Obj
|
|||||||
glog.V(4).Infof("reactor got operation %q on %q", action.GetVerb(), action.GetResource())
|
glog.V(4).Infof("reactor got operation %q on %q", action.GetVerb(), action.GetResource())
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case action.Matches("create", "persistentvolumes"):
|
||||||
|
obj := action.(core.UpdateAction).GetObject()
|
||||||
|
volume := obj.(*api.PersistentVolume)
|
||||||
|
|
||||||
|
// check the volume does not exist
|
||||||
|
_, found := r.volumes[volume.Name]
|
||||||
|
if found {
|
||||||
|
return true, nil, fmt.Errorf("Cannot create volume %s: volume already exists", volume.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the updated object to appropriate places.
|
||||||
|
if r.volumeSource != nil {
|
||||||
|
r.volumeSource.Add(volume)
|
||||||
|
}
|
||||||
|
r.volumes[volume.Name] = volume
|
||||||
|
r.changedObjects = append(r.changedObjects, volume)
|
||||||
|
r.changedSinceLastSync++
|
||||||
|
glog.V(4).Infof("created volume %s", volume.Name)
|
||||||
|
return true, volume, nil
|
||||||
|
|
||||||
case action.Matches("update", "persistentvolumes"):
|
case action.Matches("update", "persistentvolumes"):
|
||||||
obj := action.(core.UpdateAction).GetObject()
|
obj := action.(core.UpdateAction).GetObject()
|
||||||
volume := obj.(*api.PersistentVolume)
|
volume := obj.(*api.PersistentVolume)
|
||||||
@ -446,6 +466,13 @@ func addDeletePlugin(ctrl *PersistentVolumeController, expectedDeleteCalls []err
|
|||||||
ctrl.recyclePluginMgr.InitPlugins([]vol.VolumePlugin{plugin}, ctrl)
|
ctrl.recyclePluginMgr.InitPlugins([]vol.VolumePlugin{plugin}, ctrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addProvisionPlugin(ctrl *PersistentVolumeController, expectedDeleteCalls []error) {
|
||||||
|
plugin := &mockVolumePlugin{
|
||||||
|
provisionCalls: expectedDeleteCalls,
|
||||||
|
}
|
||||||
|
ctrl.provisioner = plugin
|
||||||
|
}
|
||||||
|
|
||||||
// newVolume returns a new volume with given attributes
|
// newVolume returns a new volume with given attributes
|
||||||
func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase api.PersistentVolumePhase, reclaimPolicy api.PersistentVolumeReclaimPolicy, annotations ...string) *api.PersistentVolume {
|
func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase api.PersistentVolumePhase, reclaimPolicy api.PersistentVolumeReclaimPolicy, annotations ...string) *api.PersistentVolume {
|
||||||
volume := api.PersistentVolume{
|
volume := api.PersistentVolume{
|
||||||
@ -481,7 +508,11 @@ func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase a
|
|||||||
if len(annotations) > 0 {
|
if len(annotations) > 0 {
|
||||||
volume.Annotations = make(map[string]string)
|
volume.Annotations = make(map[string]string)
|
||||||
for _, a := range annotations {
|
for _, a := range annotations {
|
||||||
volume.Annotations[a] = "yes"
|
if a != annDynamicallyProvisioned {
|
||||||
|
volume.Annotations[a] = "yes"
|
||||||
|
} else {
|
||||||
|
volume.Annotations[a] = mockPluginName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,10 +590,11 @@ type operationType string
|
|||||||
|
|
||||||
const operationDelete = "Delete"
|
const operationDelete = "Delete"
|
||||||
const operationRecycle = "Recycle"
|
const operationRecycle = "Recycle"
|
||||||
|
const operationProvision = "Provision"
|
||||||
|
|
||||||
// wrapTestWithControllerConfig returns a testCall that:
|
// wrapTestWithControllerConfig returns a testCall that:
|
||||||
// - configures controller with recycler or deleter which will return provided
|
// - configures controller with recycler, deleter or provisioner which will
|
||||||
// errors when a volume is deleted or recycled.
|
// return provided errors when a volume is deleted, recycled or provisioned
|
||||||
// - calls given testCall
|
// - calls given testCall
|
||||||
func wrapTestWithControllerConfig(operation operationType, expectedOperationCalls []error, toWrap testCall) testCall {
|
func wrapTestWithControllerConfig(operation operationType, expectedOperationCalls []error, toWrap testCall) testCall {
|
||||||
expected := expectedOperationCalls
|
expected := expectedOperationCalls
|
||||||
@ -573,6 +605,8 @@ func wrapTestWithControllerConfig(operation operationType, expectedOperationCall
|
|||||||
addDeletePlugin(ctrl, expected)
|
addDeletePlugin(ctrl, expected)
|
||||||
case operationRecycle:
|
case operationRecycle:
|
||||||
addRecyclePlugin(ctrl, expected)
|
addRecyclePlugin(ctrl, expected)
|
||||||
|
case operationProvision:
|
||||||
|
addProvisionPlugin(ctrl, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
return toWrap(ctrl, reactor, test)
|
return toWrap(ctrl, reactor, test)
|
||||||
@ -798,7 +832,7 @@ func (plugin *mockVolumePlugin) Init(host vol.VolumeHost) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *mockVolumePlugin) Name() string {
|
func (plugin *mockVolumePlugin) Name() string {
|
||||||
return "mockVolumePlugin"
|
return mockPluginName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *mockVolumePlugin) CanSupport(spec *vol.Spec) bool {
|
func (plugin *mockVolumePlugin) CanSupport(spec *vol.Spec) bool {
|
||||||
@ -834,26 +868,19 @@ func (plugin *mockVolumePlugin) Provision() (*api.PersistentVolume, error) {
|
|||||||
var pv *api.PersistentVolume
|
var pv *api.PersistentVolume
|
||||||
err := plugin.provisionCalls[plugin.provisionCallCounter]
|
err := plugin.provisionCalls[plugin.provisionCallCounter]
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Create a fake PV
|
// Create a fake PV with known GCE volume (to match expected volume)
|
||||||
fullpath := fmt.Sprintf("/tmp/hostpath_pv/%s", util.NewUUID())
|
|
||||||
|
|
||||||
pv = &api.PersistentVolume{
|
pv = &api.PersistentVolume{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
Name: plugin.provisionOptions.PVName,
|
Name: plugin.provisionOptions.PVName,
|
||||||
Annotations: map[string]string{
|
|
||||||
"kubernetes.io/createdby": "hostpath-dynamic-provisioner",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Spec: api.PersistentVolumeSpec{
|
Spec: api.PersistentVolumeSpec{
|
||||||
PersistentVolumeReclaimPolicy: plugin.provisionOptions.PersistentVolumeReclaimPolicy,
|
|
||||||
AccessModes: plugin.provisionOptions.AccessModes,
|
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceName(api.ResourceStorage): plugin.provisionOptions.Capacity,
|
api.ResourceName(api.ResourceStorage): plugin.provisionOptions.Capacity,
|
||||||
},
|
},
|
||||||
|
AccessModes: plugin.provisionOptions.AccessModes,
|
||||||
|
PersistentVolumeReclaimPolicy: plugin.provisionOptions.PersistentVolumeReclaimPolicy,
|
||||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||||
HostPath: &api.HostPathVolumeSource{
|
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
|
||||||
Path: fullpath,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
"11-1 - successful provision",
|
||||||
|
novolumes,
|
||||||
|
newVolumeArray("pv-provisioned-for-uid11-1", "1Gi", "uid11-1", "claim11-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
||||||
|
newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
// Binding will be completed in the next syncClaim
|
||||||
|
newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
noevents, wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Provision failure - plugin not found
|
||||||
|
"11-2 - plugin not found",
|
||||||
|
novolumes,
|
||||||
|
novolumes,
|
||||||
|
newClaimArray("claim11-2", "uid11-2", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
newClaimArray("claim11-2", "uid11-2", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
[]string{"Warning ProvisioningFailed"},
|
||||||
|
testSyncClaim,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Provision failure - newProvisioner returns error
|
||||||
|
"11-3 - newProvisioner failure",
|
||||||
|
novolumes,
|
||||||
|
novolumes,
|
||||||
|
newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
[]string{"Warning ProvisioningFailed"},
|
||||||
|
wrapTestWithControllerConfig(operationProvision, []error{}, testSyncClaim),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Provision failure - Provision returns error
|
||||||
|
"11-4 - provision failure",
|
||||||
|
novolumes,
|
||||||
|
novolumes,
|
||||||
|
newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
[]string{"Warning ProvisioningFailed"},
|
||||||
|
wrapTestWithControllerConfig(operationProvision, []error{errors.New("Moc provisioner error")}, testSyncClaim),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Provision success - there is already a volume available, still
|
||||||
|
// we provision a new one when requested.
|
||||||
|
"11-6 - provisioning when there is a volume available",
|
||||||
|
newVolumeArray("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain),
|
||||||
|
[]*api.PersistentVolume{
|
||||||
|
newVolume("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain),
|
||||||
|
newVolume("pv-provisioned-for-uid11-6", "1Gi", "uid11-6", "claim11-6", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
||||||
|
},
|
||||||
|
newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
// Binding will be completed in the next syncClaim
|
||||||
|
newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
noevents,
|
||||||
|
// No provisioning plugin confingure - makes the test fail when
|
||||||
|
// the controller errorneously tries to provision something
|
||||||
|
wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
// Provision success? - claim is bound before provisioner creates
|
||||||
|
// a volume.
|
||||||
|
"11-7 - claim is bound before provisioning",
|
||||||
|
novolumes,
|
||||||
|
novolumes,
|
||||||
|
[]*api.PersistentVolumeClaim{
|
||||||
|
newClaim("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
},
|
||||||
|
[]*api.PersistentVolumeClaim{
|
||||||
|
newClaim("claim11-7", "uid11-7", "1Gi", "volume11-7", api.ClaimBound, annClass, annBindCompleted),
|
||||||
|
},
|
||||||
|
[]string{"Warning ProvisioningFailed"},
|
||||||
|
getSyncClaimWithOperation(operationProvision, []error{errors.New("Moc provisioner error")}),
|
||||||
|
}, */
|
||||||
|
}
|
||||||
|
runSyncTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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("pv-provisioned-for-uid12-1", "1Gi", "uid12-1", "claim12-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
|
||||||
|
newClaimArray("claim12-1", "uid12-1", "1Gi", "", api.ClaimPending, annClass),
|
||||||
|
// Binding will be completed in the next syncClaim
|
||||||
|
newClaimArray("claim12-1", "uid12-1", "1Gi", "pv-provisioned-for-uid12-1", api.ClaimBound, annClass, annBoundByController, annBindCompleted),
|
||||||
|
noevents, wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runMultisyncTests(t, tests)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user