Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
7f2f7aa0
Commit
7f2f7aa0
authored
Nov 25, 2015
by
k8s-merge-robot
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #16432 from markturansky/recycler_race
Auto commit by PR queue bot
parents
46eca25d
b9b8cf7f
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
190 additions
and
149 deletions
+190
-149
persistentvolume_claim_binder_controller.go
...sistentvolume/persistentvolume_claim_binder_controller.go
+86
-84
persistentvolume_claim_binder_controller_test.go
...ntvolume/persistentvolume_claim_binder_controller_test.go
+0
-0
persistentvolume_index_test.go
...ontroller/persistentvolume/persistentvolume_index_test.go
+34
-46
types.go
pkg/controller/persistentvolume/types.go
+21
-17
persistent_volumes_test.go
test/integration/persistent_volumes_test.go
+49
-2
No files found.
pkg/controller/persistentvolume/persistentvolume_claim_binder_controller.go
View file @
7f2f7aa0
...
@@ -27,6 +27,7 @@ import (
...
@@ -27,6 +27,7 @@ import (
"k8s.io/kubernetes/pkg/client/cache"
"k8s.io/kubernetes/pkg/client/cache"
client
"k8s.io/kubernetes/pkg/client/unversioned"
client
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/controller/framework"
"k8s.io/kubernetes/pkg/controller/framework"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime"
...
@@ -159,44 +160,53 @@ func syncVolume(volumeIndex *persistentVolumeOrderedIndex, binderClient binderCl
...
@@ -159,44 +160,53 @@ func syncVolume(volumeIndex *persistentVolumeOrderedIndex, binderClient binderCl
currentPhase
:=
volume
.
Status
.
Phase
currentPhase
:=
volume
.
Status
.
Phase
nextPhase
:=
currentPhase
nextPhase
:=
currentPhase
_
,
exists
,
err
:=
volumeIndex
.
Get
(
volume
)
if
err
!=
nil
{
return
err
}
if
!
exists
{
volumeIndex
.
Add
(
volume
)
}
switch
currentPhase
{
switch
currentPhase
{
// pending volumes are available only after indexing in order to be matched to claims.
case
api
.
VolumePending
:
case
api
.
VolumePending
:
// 3 possible states:
// 1. ClaimRef != nil and Claim exists: Prebound to claim. Make volume available for binding (it will match PVC).
// 2. ClaimRef != nil and Claim !exists: Recently recycled. Remove bind. Make volume available for new claim.
// 3. ClaimRef == nil: Neither recycled nor prebound. Make volume available for binding.
nextPhase
=
api
.
VolumeAvailable
if
volume
.
Spec
.
ClaimRef
!=
nil
{
if
volume
.
Spec
.
ClaimRef
!=
nil
{
// Pending volumes that have a ClaimRef were recently recycled. The Recycler set the phase to VolumePending
_
,
err
:=
binderClient
.
GetPersistentVolumeClaim
(
volume
.
Spec
.
ClaimRef
.
Namespace
,
volume
.
Spec
.
ClaimRef
.
Name
)
// to start the volume again at the beginning of this lifecycle.
if
errors
.
IsNotFound
(
err
)
{
// ClaimRef is the last bind between persistent volume and claim.
// Pending volumes that have a ClaimRef where the claim is missing were recently recycled.
// The claim has already been deleted by the user at this point
// The Recycler set the phase to VolumePending to start the volume at the beginning of this lifecycle.
oldClaimRef
:=
volume
.
Spec
.
ClaimRef
// removing ClaimRef unbinds the volume
volume
.
Spec
.
ClaimRef
=
nil
clone
,
err
:=
conversion
.
NewCloner
()
.
DeepCopy
(
volume
)
_
,
err
=
binderClient
.
UpdatePersistentVolume
(
volume
)
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Error cloning pv: %v"
,
err
)
// rollback on error, keep the ClaimRef until we can successfully update the volume
}
volume
.
Spec
.
ClaimRef
=
oldClaimRef
volumeClone
,
ok
:=
clone
.
(
*
api
.
PersistentVolume
)
return
fmt
.
Errorf
(
"Unexpected error saving PersistentVolume: %+v"
,
err
)
if
!
ok
{
}
return
fmt
.
Errorf
(
"Unexpected pv cast error : %v
\n
"
,
volumeClone
)
}
}
volumeClone
.
Spec
.
ClaimRef
=
nil
_
,
exists
,
err
:=
volumeIndex
.
Get
(
volume
)
if
updatedVolume
,
err
:=
binderClient
.
UpdatePersistentVolume
(
volumeClone
);
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Unexpected error saving PersistentVolume: %+v"
,
err
)
return
err
}
else
{
}
volume
=
updatedVolume
if
!
exists
{
volumeIndex
.
Update
(
volume
)
volumeIndex
.
Add
(
volume
)
}
}
else
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Error getting PersistentVolumeClaim[%s/%s]: %v"
,
volume
.
Spec
.
ClaimRef
.
Namespace
,
volume
.
Spec
.
ClaimRef
.
Name
,
err
)
}
}
}
glog
.
V
(
5
)
.
Infof
(
"PersistentVolume[%s] is now available
\n
"
,
volume
.
Name
)
glog
.
V
(
5
)
.
Infof
(
"PersistentVolume[%s] is available
\n
"
,
volume
.
Name
)
nextPhase
=
api
.
VolumeAvailable
// available volumes await a claim
// available volumes await a claim
case
api
.
VolumeAvailable
:
case
api
.
VolumeAvailable
:
// TODO: remove api.VolumePending phase altogether
_
,
exists
,
err
:=
volumeIndex
.
Get
(
volume
)
if
err
!=
nil
{
return
err
}
if
!
exists
{
volumeIndex
.
Add
(
volume
)
}
if
volume
.
Spec
.
ClaimRef
!=
nil
{
if
volume
.
Spec
.
ClaimRef
!=
nil
{
_
,
err
:=
binderClient
.
GetPersistentVolumeClaim
(
volume
.
Spec
.
ClaimRef
.
Namespace
,
volume
.
Spec
.
ClaimRef
.
Name
)
_
,
err
:=
binderClient
.
GetPersistentVolumeClaim
(
volume
.
Spec
.
ClaimRef
.
Namespace
,
volume
.
Spec
.
ClaimRef
.
Name
)
if
err
==
nil
{
if
err
==
nil
{
...
@@ -265,79 +275,71 @@ func syncVolume(volumeIndex *persistentVolumeOrderedIndex, binderClient binderCl
...
@@ -265,79 +275,71 @@ func syncVolume(volumeIndex *persistentVolumeOrderedIndex, binderClient binderCl
func
syncClaim
(
volumeIndex
*
persistentVolumeOrderedIndex
,
binderClient
binderClient
,
claim
*
api
.
PersistentVolumeClaim
)
(
err
error
)
{
func
syncClaim
(
volumeIndex
*
persistentVolumeOrderedIndex
,
binderClient
binderClient
,
claim
*
api
.
PersistentVolumeClaim
)
(
err
error
)
{
glog
.
V
(
5
)
.
Infof
(
"Synchronizing PersistentVolumeClaim[%s]
\n
"
,
claim
.
Name
)
glog
.
V
(
5
)
.
Infof
(
"Synchronizing PersistentVolumeClaim[%s]
\n
"
,
claim
.
Name
)
// claims can be in one of the following states:
switch
claim
.
Status
.
Phase
{
//
// ClaimPending -- default value -- not bound to a claim. A volume that matches the claim may not exist.
// ClaimBound -- bound to a volume. claim.Status.VolumeRef != nil
currentPhase
:=
claim
.
Status
.
Phase
nextPhase
:=
currentPhase
switch
currentPhase
{
// pending claims await a matching volume
case
api
.
ClaimPending
:
case
api
.
ClaimPending
:
volume
,
err
:=
volumeIndex
.
F
indBestMatchForClaim
(
claim
)
volume
,
err
:=
volumeIndex
.
f
indBestMatchForClaim
(
claim
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
if
volume
==
nil
{
if
volume
==
nil
{
return
fmt
.
Errorf
(
"A volume match does not exist for persistent claim: %s"
,
claim
.
Name
)
glog
.
V
(
5
)
.
Infof
(
"A volume match does not exist for persistent claim: %s"
,
claim
.
Name
)
return
nil
}
}
// make a binding reference to the claim.
// create a reference to the claim and assign it to the volume being bound.
// triggers update of the claim in this controller, which builds claim status
// the volume is a pointer and assigning the reference fixes a race condition where another
claim
.
Spec
.
VolumeName
=
volume
.
Name
// claim might match this volume but before the claimRef is persistent in the next case statement
// TODO: make this similar to Pod's binding both with BindingREST subresource and GuaranteedUpdate helper in etcd.go
claimRef
,
err
:=
api
.
GetReference
(
claim
)
claim
,
err
=
binderClient
.
UpdatePersistentVolumeClaim
(
claim
)
if
err
!=
nil
{
if
err
==
nil
{
return
fmt
.
Errorf
(
"Unexpected error getting claim reference: %v
\n
"
,
err
)
nextPhase
=
api
.
ClaimBound
glog
.
V
(
5
)
.
Infof
(
"PersistentVolumeClaim[%s] is bound
\n
"
,
claim
.
Name
)
}
else
{
// Rollback by unsetting the ClaimRef on the volume pointer.
// the volume in the index will be unbound again and ready to be matched.
claim
.
Spec
.
VolumeName
=
""
// Rollback by restoring original phase to claim pointer
nextPhase
=
api
.
ClaimPending
return
fmt
.
Errorf
(
"Error updating volume: %+v
\n
"
,
err
)
}
}
case
api
.
ClaimBound
:
// make a binding reference to the claim and ensure to update the local index to prevent dupe bindings
volume
,
err
:=
binderClient
.
GetPersistentVolume
(
claim
.
Spec
.
VolumeNa
me
)
clone
,
err
:=
conversion
.
NewCloner
()
.
DeepCopy
(
volu
me
)
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Unexpected error getting persistent volume: %v
\n
"
,
err
)
return
fmt
.
Errorf
(
"Error cloning pv: %v"
,
err
)
}
volumeClone
,
ok
:=
clone
.
(
*
api
.
PersistentVolume
)
if
!
ok
{
return
fmt
.
Errorf
(
"Unexpected pv cast error : %v
\n
"
,
volumeClone
)
}
volumeClone
.
Spec
.
ClaimRef
=
claimRef
if
updatedVolume
,
err
:=
binderClient
.
UpdatePersistentVolume
(
volumeClone
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Unexpected error saving PersistentVolume.Status: %+v"
,
err
)
}
else
{
volume
=
updatedVolume
volumeIndex
.
Update
(
updatedVolume
)
}
}
if
volume
.
Spec
.
ClaimRef
==
nil
{
// the bind is persisted on the volume above and will always match the claim in a search.
glog
.
V
(
5
)
.
Infof
(
"Rebuilding bind on pv.Spec.ClaimRef
\n
"
)
// claim would remain Pending if the update fails, so processing this state is idempotent.
claimRef
,
err
:=
api
.
GetReference
(
claim
)
// this only needs to be processed once.
if
claim
.
Spec
.
VolumeName
!=
volume
.
Name
{
claim
.
Spec
.
VolumeName
=
volume
.
Name
claim
,
err
=
binderClient
.
UpdatePersistentVolumeClaim
(
claim
)
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Unexpected error getting claim reference: %v
\n
"
,
err
)
return
fmt
.
Errorf
(
"Error updating claim with VolumeName %s: %+v
\n
"
,
volume
.
Name
,
err
)
}
volume
.
Spec
.
ClaimRef
=
claimRef
_
,
err
=
binderClient
.
UpdatePersistentVolume
(
volume
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Unexpected error saving PersistentVolume.Status: %+v"
,
err
)
}
}
}
}
// all "actuals" are transferred from PV to PVC so the user knows what
claim
.
Status
.
Phase
=
api
.
ClaimBound
// type of volume they actually got for their claim.
claim
.
Status
.
AccessModes
=
volume
.
Spec
.
AccessModes
// Volumes cannot have zero AccessModes, so checking that a claim has access modes
claim
.
Status
.
Capacity
=
volume
.
Spec
.
Capacity
// is sufficient to tell us if these values have already been set.
_
,
err
=
binderClient
.
UpdatePersistentVolumeClaimStatus
(
claim
)
if
len
(
claim
.
Status
.
AccessModes
)
==
0
{
if
err
!=
nil
{
claim
.
Status
.
Phase
=
api
.
ClaimBound
return
fmt
.
Errorf
(
"Unexpected error saving claim status: %+v"
,
err
)
claim
.
Status
.
AccessModes
=
volume
.
Spec
.
AccessModes
claim
.
Status
.
Capacity
=
volume
.
Spec
.
Capacity
_
,
err
:=
binderClient
.
UpdatePersistentVolumeClaimStatus
(
claim
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Unexpected error saving claim status: %+v"
,
err
)
}
}
}
}
if
currentPhase
!=
nextPhase
{
case
api
.
ClaimBound
:
claim
.
Status
.
Phase
=
nextPhase
// no-op. Claim is bound, values from PV are set. PVCs are technically mutable in the API server
binderClient
.
UpdatePersistentVolumeClaimStatus
(
claim
)
// and we don't want to handle those changes at this time.
default
:
return
fmt
.
Errorf
(
"Unknown state for PVC: %#v"
,
claim
)
}
}
glog
.
V
(
5
)
.
Infof
(
"PersistentVolumeClaim[%s] is bound
\n
"
,
claim
.
Name
)
return
nil
return
nil
}
}
...
...
pkg/controller/persistentvolume/persistentvolume_claim_binder_controller_test.go
View file @
7f2f7aa0
This diff is collapsed.
Click to expand it.
pkg/controller/persistentvolume/persistentvolume_index_test.go
View file @
7f2f7aa0
...
@@ -21,6 +21,7 @@ import (
...
@@ -21,6 +21,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/testapi"
)
)
func
TestMatchVolume
(
t
*
testing
.
T
)
{
func
TestMatchVolume
(
t
*
testing
.
T
)
{
...
@@ -104,17 +105,17 @@ func TestMatchVolume(t *testing.T) {
...
@@ -104,17 +105,17 @@ func TestMatchVolume(t *testing.T) {
}
}
for
name
,
scenario
:=
range
scenarios
{
for
name
,
scenario
:=
range
scenarios
{
volume
,
err
:=
volList
.
F
indBestMatchForClaim
(
scenario
.
claim
)
volume
,
err
:=
volList
.
f
indBestMatchForClaim
(
scenario
.
claim
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Errorf
(
"Unexpected error matching volume by claim: %v"
,
err
)
t
.
Errorf
(
"Unexpected error matching volume by claim: %v"
,
err
)
}
}
if
scenario
.
expectedMatch
!=
""
&&
volume
==
nil
{
if
len
(
scenario
.
expectedMatch
)
!=
0
&&
volume
==
nil
{
t
.
Errorf
(
"Expected match but received nil volume for scenario: %s"
,
name
)
t
.
Errorf
(
"Expected match but received nil volume for scenario: %s"
,
name
)
}
}
if
scenario
.
expectedMatch
!=
""
&&
volume
!=
nil
&&
string
(
volume
.
UID
)
!=
scenario
.
expectedMatch
{
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
)
t
.
Errorf
(
"Expected %s but got volume %s in scenario %s"
,
scenario
.
expectedMatch
,
volume
.
UID
,
name
)
}
}
if
scenario
.
expectedMatch
==
""
&&
volume
!=
nil
{
if
len
(
scenario
.
expectedMatch
)
==
0
&&
volume
!=
nil
{
t
.
Errorf
(
"Unexpected match for scenario: %s"
,
name
)
t
.
Errorf
(
"Unexpected match for scenario: %s"
,
name
)
}
}
}
}
...
@@ -175,7 +176,7 @@ func TestMatchingWithBoundVolumes(t *testing.T) {
...
@@ -175,7 +176,7 @@ func TestMatchingWithBoundVolumes(t *testing.T) {
},
},
}
}
volume
,
err
:=
volumeIndex
.
F
indBestMatchForClaim
(
claim
)
volume
,
err
:=
volumeIndex
.
f
indBestMatchForClaim
(
claim
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatalf
(
"Unexpected error matching volume by claim: %v"
,
err
)
t
.
Fatalf
(
"Unexpected error matching volume by claim: %v"
,
err
)
}
}
...
@@ -296,27 +297,27 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
...
@@ -296,27 +297,27 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
index
.
Add
(
ebs
)
index
.
Add
(
ebs
)
index
.
Add
(
nfs
)
index
.
Add
(
nfs
)
volume
,
_
:=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
:=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
ebs
.
Name
{
if
volume
.
Name
!=
ebs
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
ebs
.
Name
,
volume
.
Name
)
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
ebs
.
Name
,
volume
.
Name
)
}
}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
,
api
.
ReadOnlyMany
}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
,
api
.
ReadOnlyMany
}
volume
,
_
=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
gce
.
Name
{
if
volume
.
Name
!=
gce
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
gce
.
Name
,
volume
.
Name
)
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
gce
.
Name
,
volume
.
Name
)
}
}
// order of the requested modes should not matter
// order of the requested modes should not matter
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteMany
,
api
.
ReadWriteOnce
,
api
.
ReadOnlyMany
}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteMany
,
api
.
ReadWriteOnce
,
api
.
ReadOnlyMany
}
volume
,
_
=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
nfs
.
Name
{
if
volume
.
Name
!=
nfs
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
nfs
.
Name
,
volume
.
Name
)
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
nfs
.
Name
,
volume
.
Name
)
}
}
// fewer modes requested should still match
// fewer modes requested should still match
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteMany
}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteMany
}
volume
,
_
=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
nfs
.
Name
{
if
volume
.
Name
!=
nfs
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
nfs
.
Name
,
volume
.
Name
)
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
nfs
.
Name
,
volume
.
Name
)
}
}
...
@@ -324,7 +325,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
...
@@ -324,7 +325,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
// pretend the exact match is bound. should get the next level up of modes.
// pretend the exact match is bound. should get the next level up of modes.
ebs
.
Spec
.
ClaimRef
=
&
api
.
ObjectReference
{}
ebs
.
Spec
.
ClaimRef
=
&
api
.
ObjectReference
{}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
}
volume
,
_
=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
gce
.
Name
{
if
volume
.
Name
!=
gce
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
gce
.
Name
,
volume
.
Name
)
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
gce
.
Name
,
volume
.
Name
)
}
}
...
@@ -332,7 +333,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
...
@@ -332,7 +333,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
// continue up the levels of modes.
// continue up the levels of modes.
gce
.
Spec
.
ClaimRef
=
&
api
.
ObjectReference
{}
gce
.
Spec
.
ClaimRef
=
&
api
.
ObjectReference
{}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
}
volume
,
_
=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
nfs
.
Name
{
if
volume
.
Name
!=
nfs
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
nfs
.
Name
,
volume
.
Name
)
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
nfs
.
Name
,
volume
.
Name
)
}
}
...
@@ -340,7 +341,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
...
@@ -340,7 +341,7 @@ func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
// partial mode request
// partial mode request
gce
.
Spec
.
ClaimRef
=
nil
gce
.
Spec
.
ClaimRef
=
nil
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadOnlyMany
}
claim
.
Spec
.
AccessModes
=
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadOnlyMany
}
volume
,
_
=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
gce
.
Name
{
if
volume
.
Name
!=
gce
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
gce
.
Name
,
volume
.
Name
)
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
gce
.
Name
,
volume
.
Name
)
}
}
...
@@ -485,53 +486,40 @@ func createTestVolumes() []*api.PersistentVolume {
...
@@ -485,53 +486,40 @@ func createTestVolumes() []*api.PersistentVolume {
}
}
}
}
func
TestFindingPreboundVolumes
(
t
*
testing
.
T
)
{
func
testVolume
(
name
,
size
string
)
*
api
.
PersistentVolume
{
pv1
:=
&
api
.
PersistentVolume
{
return
&
api
.
PersistentVolume
{
ObjectMeta
:
api
.
ObjectMeta
{
ObjectMeta
:
api
.
ObjectMeta
{
Name
:
"pv1"
,
Name
:
name
,
Annotations
:
map
[
string
]
string
{},
Annotations
:
map
[
string
]
string
{},
},
},
Spec
:
api
.
PersistentVolumeSpec
{
Spec
:
api
.
PersistentVolumeSpec
{
Capacity
:
api
.
ResourceList
{
api
.
ResourceName
(
api
.
ResourceStorage
)
:
resource
.
MustParse
(
"1Gi"
)},
Capacity
:
api
.
ResourceList
{
api
.
ResourceName
(
api
.
ResourceStorage
)
:
resource
.
MustParse
(
size
)},
PersistentVolumeSource
:
api
.
PersistentVolumeSource
{
HostPath
:
&
api
.
HostPathVolumeSource
{}},
AccessModes
:
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
},
},
}
pv5
:=
&
api
.
PersistentVolume
{
ObjectMeta
:
api
.
ObjectMeta
{
Name
:
"pv5"
,
Annotations
:
map
[
string
]
string
{},
},
Spec
:
api
.
PersistentVolumeSpec
{
Capacity
:
api
.
ResourceList
{
api
.
ResourceName
(
api
.
ResourceStorage
)
:
resource
.
MustParse
(
"5Gi"
)},
PersistentVolumeSource
:
api
.
PersistentVolumeSource
{
HostPath
:
&
api
.
HostPathVolumeSource
{}},
AccessModes
:
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
},
},
}
pv8
:=
&
api
.
PersistentVolume
{
ObjectMeta
:
api
.
ObjectMeta
{
Name
:
"pv8"
,
Annotations
:
map
[
string
]
string
{},
},
Spec
:
api
.
PersistentVolumeSpec
{
Capacity
:
api
.
ResourceList
{
api
.
ResourceName
(
api
.
ResourceStorage
)
:
resource
.
MustParse
(
"8Gi"
)},
PersistentVolumeSource
:
api
.
PersistentVolumeSource
{
HostPath
:
&
api
.
HostPathVolumeSource
{}},
PersistentVolumeSource
:
api
.
PersistentVolumeSource
{
HostPath
:
&
api
.
HostPathVolumeSource
{}},
AccessModes
:
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
},
AccessModes
:
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
},
},
},
}
}
}
func
TestFindingPreboundVolumes
(
t
*
testing
.
T
)
{
claim
:=
&
api
.
PersistentVolumeClaim
{
claim
:=
&
api
.
PersistentVolumeClaim
{
ObjectMeta
:
api
.
ObjectMeta
{
ObjectMeta
:
api
.
ObjectMeta
{
Name
:
"claim01"
,
Name
:
"claim01"
,
Namespace
:
"myns"
,
Namespace
:
"myns"
,
SelfLink
:
testapi
.
Default
.
SelfLink
(
"pvc"
,
""
),
},
},
Spec
:
api
.
PersistentVolumeClaimSpec
{
Spec
:
api
.
PersistentVolumeClaimSpec
{
AccessModes
:
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
},
AccessModes
:
[]
api
.
PersistentVolumeAccessMode
{
api
.
ReadWriteOnce
},
Resources
:
api
.
ResourceRequirements
{
Requests
:
api
.
ResourceList
{
api
.
ResourceName
(
api
.
ResourceStorage
)
:
resource
.
MustParse
(
"1Gi"
)}},
Resources
:
api
.
ResourceRequirements
{
Requests
:
api
.
ResourceList
{
api
.
ResourceName
(
api
.
ResourceStorage
)
:
resource
.
MustParse
(
"1Gi"
)}},
},
},
}
}
claimRef
,
err
:=
api
.
GetReference
(
claim
)
if
err
!=
nil
{
t
.
Errorf
(
"error getting claimRef: %v"
,
err
)
}
pv1
:=
testVolume
(
"pv1"
,
"1Gi"
)
pv5
:=
testVolume
(
"pv5"
,
"5Gi"
)
pv8
:=
testVolume
(
"pv8"
,
"8Gi"
)
index
:=
NewPersistentVolumeOrderedIndex
()
index
:=
NewPersistentVolumeOrderedIndex
()
index
.
Add
(
pv1
)
index
.
Add
(
pv1
)
...
@@ -539,22 +527,22 @@ func TestFindingPreboundVolumes(t *testing.T) {
...
@@ -539,22 +527,22 @@ func TestFindingPreboundVolumes(t *testing.T) {
index
.
Add
(
pv8
)
index
.
Add
(
pv8
)
// expected exact match on size
// expected exact match on size
volume
,
_
:=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
:=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
pv1
.
Name
{
if
volume
.
Name
!=
pv1
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
pv1
.
Name
,
volume
.
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.
// pretend the exact match is pre-bound. should get the next size up.
pv1
.
Annotations
[
createdForKey
]
=
"some/other/claim"
pv1
.
Spec
.
ClaimRef
=
&
api
.
ObjectReference
{
Name
:
"foo"
,
Namespace
:
"bar"
}
volume
,
_
=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
pv5
.
Name
{
if
volume
.
Name
!=
pv5
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
pv5
.
Name
,
volume
.
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.
// pretend the exact match is available but the largest volume is pre-bound to the claim.
delete
(
pv1
.
Annotations
,
createdForKey
)
pv1
.
Spec
.
ClaimRef
=
nil
pv8
.
Annotations
[
createdForKey
]
=
"myns/claim01"
pv8
.
Spec
.
ClaimRef
=
claimRef
volume
,
_
=
index
.
F
indBestMatchForClaim
(
claim
)
volume
,
_
=
index
.
f
indBestMatchForClaim
(
claim
)
if
volume
.
Name
!=
pv8
.
Name
{
if
volume
.
Name
!=
pv8
.
Name
{
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
pv8
.
Name
,
volume
.
Name
)
t
.
Errorf
(
"Expected %s but got volume %s instead"
,
pv8
.
Name
,
volume
.
Name
)
}
}
...
...
pkg/controller/persistentvolume/types.go
View file @
7f2f7aa0
...
@@ -79,8 +79,8 @@ func (pvIndex *persistentVolumeOrderedIndex) ListByAccessModes(modes []api.Persi
...
@@ -79,8 +79,8 @@ func (pvIndex *persistentVolumeOrderedIndex) ListByAccessModes(modes []api.Persi
// matchPredicate is a function that indicates that a persistent volume matches another
// matchPredicate is a function that indicates that a persistent volume matches another
type
matchPredicate
func
(
compareThis
,
toThis
*
api
.
PersistentVolume
)
bool
type
matchPredicate
func
(
compareThis
,
toThis
*
api
.
PersistentVolume
)
bool
//
F
ind returns the nearest PV from the ordered list or nil if a match is not found
//
f
ind returns the nearest PV from the ordered list or nil if a match is not found
func
(
pvIndex
*
persistentVolumeOrderedIndex
)
F
ind
(
searchPV
*
api
.
PersistentVolume
,
matchPredicate
matchPredicate
)
(
*
api
.
PersistentVolume
,
error
)
{
func
(
pvIndex
*
persistentVolumeOrderedIndex
)
f
ind
(
searchPV
*
api
.
PersistentVolume
,
matchPredicate
matchPredicate
)
(
*
api
.
PersistentVolume
,
error
)
{
// the 'searchPV' argument is a synthetic PV with capacity and accessmodes set according to the user's PersistentVolumeClaim.
// the 'searchPV' argument is a synthetic PV with capacity and accessmodes set according to the user's PersistentVolumeClaim.
// the synthetic pv arg is, therefore, a request for a storage resource.
// the synthetic pv arg is, therefore, a request for a storage resource.
//
//
...
@@ -94,6 +94,16 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(searchPV *api.PersistentVolume
...
@@ -94,6 +94,16 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(searchPV *api.PersistentVolume
// potential matches (the GCEPD example above).
// potential matches (the GCEPD example above).
allPossibleModes
:=
pvIndex
.
allPossibleMatchingAccessModes
(
searchPV
.
Spec
.
AccessModes
)
allPossibleModes
:=
pvIndex
.
allPossibleMatchingAccessModes
(
searchPV
.
Spec
.
AccessModes
)
// the searchPV should contain an annotation that allows pre-binding to a claim.
// we can use the same annotation value (pvc's namespace/name) and check against
// existing volumes to find an exact match. It is possible that a bind is made (ClaimRef persisted to PV)
// but the fail to update claim.Spec.VolumeName fails. This check allows the claim to find the volume
// that's already bound to the claim.
preboundClaim
:=
""
if
createdFor
,
ok
:=
searchPV
.
Annotations
[
createdForKey
];
ok
{
preboundClaim
=
createdFor
}
for
_
,
modes
:=
range
allPossibleModes
{
for
_
,
modes
:=
range
allPossibleModes
{
volumes
,
err
:=
pvIndex
.
ListByAccessModes
(
modes
)
volumes
,
err
:=
pvIndex
.
ListByAccessModes
(
modes
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -105,23 +115,17 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(searchPV *api.PersistentVolume
...
@@ -105,23 +115,17 @@ func (pvIndex *persistentVolumeOrderedIndex) Find(searchPV *api.PersistentVolume
// return the exact pre-binding match, if found
// return the exact pre-binding match, if found
unboundVolumes
:=
[]
*
api
.
PersistentVolume
{}
unboundVolumes
:=
[]
*
api
.
PersistentVolume
{}
for
_
,
volume
:=
range
volumes
{
for
_
,
volume
:=
range
volumes
{
// check for current binding
if
volume
.
Spec
.
ClaimRef
==
nil
{
if
volume
.
Spec
.
ClaimRef
!=
nil
{
// volume isn't currently bound or pre-bound.
unboundVolumes
=
append
(
unboundVolumes
,
volume
)
continue
continue
}
}
// check for pre-bind where the volume is intended for one specific claim
boundClaim
:=
fmt
.
Sprintf
(
"%s/%s"
,
volume
.
Spec
.
ClaimRef
.
Namespace
,
volume
.
Spec
.
ClaimRef
.
Name
)
if
createdFor
,
ok
:=
volume
.
Annotations
[
createdForKey
];
ok
{
if
boundClaim
==
preboundClaim
{
if
createdFor
!=
searchPV
.
Annotations
[
createdForKey
]
{
// exact match! No search required.
// the volume is pre-bound and does not match the search criteria.
continue
}
// exact annotation match! No search required.
return
volume
,
nil
return
volume
,
nil
}
}
// volume isn't currently bound or pre-bound.
unboundVolumes
=
append
(
unboundVolumes
,
volume
)
}
}
i
:=
sort
.
Search
(
len
(
unboundVolumes
),
func
(
i
int
)
bool
{
return
matchPredicate
(
searchPV
,
unboundVolumes
[
i
])
})
i
:=
sort
.
Search
(
len
(
unboundVolumes
),
func
(
i
int
)
bool
{
return
matchPredicate
(
searchPV
,
unboundVolumes
[
i
])
})
...
@@ -147,11 +151,11 @@ func (pvIndex *persistentVolumeOrderedIndex) findByAccessModesAndStorageCapacity
...
@@ -147,11 +151,11 @@ func (pvIndex *persistentVolumeOrderedIndex) findByAccessModesAndStorageCapacity
},
},
},
},
}
}
return
pvIndex
.
F
ind
(
pv
,
matchStorageCapacity
)
return
pvIndex
.
f
ind
(
pv
,
matchStorageCapacity
)
}
}
//
F
indBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage
//
f
indBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage
func
(
pvIndex
*
persistentVolumeOrderedIndex
)
F
indBestMatchForClaim
(
claim
*
api
.
PersistentVolumeClaim
)
(
*
api
.
PersistentVolume
,
error
)
{
func
(
pvIndex
*
persistentVolumeOrderedIndex
)
f
indBestMatchForClaim
(
claim
*
api
.
PersistentVolumeClaim
)
(
*
api
.
PersistentVolume
,
error
)
{
return
pvIndex
.
findByAccessModesAndStorageCapacity
(
fmt
.
Sprintf
(
"%s/%s"
,
claim
.
Namespace
,
claim
.
Name
),
claim
.
Spec
.
AccessModes
,
claim
.
Spec
.
Resources
.
Requests
[
api
.
ResourceName
(
api
.
ResourceStorage
)])
return
pvIndex
.
findByAccessModesAndStorageCapacity
(
fmt
.
Sprintf
(
"%s/%s"
,
claim
.
Namespace
,
claim
.
Name
),
claim
.
Spec
.
AccessModes
,
claim
.
Spec
.
Resources
.
Requests
[
api
.
ResourceName
(
api
.
ResourceStorage
)])
}
}
...
...
test/integration/persistent_volumes_test.go
View file @
7f2f7aa0
...
@@ -19,6 +19,8 @@ limitations under the License.
...
@@ -19,6 +19,8 @@ limitations under the License.
package
integration
package
integration
import
(
import
(
"fmt"
"math/rand"
"testing"
"testing"
"time"
"time"
...
@@ -28,6 +30,7 @@ import (
...
@@ -28,6 +30,7 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/unversioned"
client
"k8s.io/kubernetes/pkg/client/unversioned"
client
"k8s.io/kubernetes/pkg/client/unversioned"
persistentvolumecontroller
"k8s.io/kubernetes/pkg/controller/persistentvolume"
persistentvolumecontroller
"k8s.io/kubernetes/pkg/controller/persistentvolume"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume"
...
@@ -48,11 +51,11 @@ func TestPersistentVolumeRecycler(t *testing.T) {
...
@@ -48,11 +51,11 @@ func TestPersistentVolumeRecycler(t *testing.T) {
recyclerClient
:=
client
.
NewOrDie
(
&
client
.
Config
{
Host
:
s
.
URL
,
GroupVersion
:
testapi
.
Default
.
GroupVersion
()})
recyclerClient
:=
client
.
NewOrDie
(
&
client
.
Config
{
Host
:
s
.
URL
,
GroupVersion
:
testapi
.
Default
.
GroupVersion
()})
testClient
:=
client
.
NewOrDie
(
&
client
.
Config
{
Host
:
s
.
URL
,
GroupVersion
:
testapi
.
Default
.
GroupVersion
()})
testClient
:=
client
.
NewOrDie
(
&
client
.
Config
{
Host
:
s
.
URL
,
GroupVersion
:
testapi
.
Default
.
GroupVersion
()})
binder
:=
persistentvolumecontroller
.
NewPersistentVolumeClaimBinder
(
binderClient
,
1
*
time
.
Second
)
binder
:=
persistentvolumecontroller
.
NewPersistentVolumeClaimBinder
(
binderClient
,
1
0
*
time
.
Minute
)
binder
.
Run
()
binder
.
Run
()
defer
binder
.
Stop
()
defer
binder
.
Stop
()
recycler
,
_
:=
persistentvolumecontroller
.
NewPersistentVolumeRecycler
(
recyclerClient
,
1
*
time
.
Second
,
[]
volume
.
VolumePlugin
{
&
volume
.
FakeVolumePlugin
{
"plugin-name"
,
volume
.
NewFakeVolumeHost
(
"/tmp/fake"
,
nil
,
nil
)}})
recycler
,
_
:=
persistentvolumecontroller
.
NewPersistentVolumeRecycler
(
recyclerClient
,
30
*
time
.
Minute
,
[]
volume
.
VolumePlugin
{
&
volume
.
FakeVolumePlugin
{
"plugin-name"
,
volume
.
NewFakeVolumeHost
(
"/tmp/fake"
,
nil
,
nil
)}})
recycler
.
Run
()
recycler
.
Run
()
defer
recycler
.
Stop
()
defer
recycler
.
Stop
()
...
@@ -122,6 +125,50 @@ func TestPersistentVolumeRecycler(t *testing.T) {
...
@@ -122,6 +125,50 @@ func TestPersistentVolumeRecycler(t *testing.T) {
break
break
}
}
}
}
// test the race between claims and volumes. ensure only a volume only binds to a single claim.
deleteAllEtcdKeys
()
counter
:=
0
maxClaims
:=
100
claims
:=
[]
*
api
.
PersistentVolumeClaim
{}
for
counter
<=
maxClaims
{
counter
+=
1
clone
,
_
:=
conversion
.
NewCloner
()
.
DeepCopy
(
pvc
)
newPvc
,
_
:=
clone
.
(
*
api
.
PersistentVolumeClaim
)
newPvc
.
ObjectMeta
=
api
.
ObjectMeta
{
Name
:
fmt
.
Sprintf
(
"fake-pvc-%d"
,
counter
)}
claim
,
err
:=
testClient
.
PersistentVolumeClaims
(
api
.
NamespaceDefault
)
.
Create
(
newPvc
)
if
err
!=
nil
{
t
.
Fatal
(
"Error creating newPvc: %v"
,
err
)
}
claims
=
append
(
claims
,
claim
)
}
// putting a bind manually on a pv should only match the claim it is bound to
rand
.
Seed
(
time
.
Now
()
.
Unix
())
claim
:=
claims
[
rand
.
Intn
(
maxClaims
-
1
)]
claimRef
,
err
:=
api
.
GetReference
(
claim
)
if
err
!=
nil
{
t
.
Fatalf
(
"Unexpected error getting claimRef: %v"
,
err
)
}
pv
.
Spec
.
ClaimRef
=
claimRef
pv
,
err
=
testClient
.
PersistentVolumes
()
.
Create
(
pv
)
if
err
!=
nil
{
t
.
Fatalf
(
"Unexpected error creating pv: %v"
,
err
)
}
waitForPersistentVolumePhase
(
w
,
api
.
VolumeBound
)
pv
,
err
=
testClient
.
PersistentVolumes
()
.
Get
(
pv
.
Name
)
if
err
!=
nil
{
t
.
Fatalf
(
"Unexpected error getting pv: %v"
,
err
)
}
if
pv
.
Spec
.
ClaimRef
==
nil
{
t
.
Fatalf
(
"Unexpected nil claimRef"
)
}
if
pv
.
Spec
.
ClaimRef
.
Namespace
!=
claimRef
.
Namespace
||
pv
.
Spec
.
ClaimRef
.
Name
!=
claimRef
.
Name
{
t
.
Fatalf
(
"Bind mismatch! Expected %s/%s but got %s/%s"
,
claimRef
.
Namespace
,
claimRef
.
Name
,
pv
.
Spec
.
ClaimRef
.
Namespace
,
pv
.
Spec
.
ClaimRef
.
Name
)
}
}
}
func
waitForPersistentVolumePhase
(
w
watch
.
Interface
,
phase
api
.
PersistentVolumePhase
)
{
func
waitForPersistentVolumePhase
(
w
watch
.
Interface
,
phase
api
.
PersistentVolumePhase
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment