Skip to content

Commit fc2929e

Browse files
author
k8s-merge-robot
committed
Merge pull request kubernetes#19600 from jsafrane/devel/fix-kubelet-detach
Auto commit by PR queue bot
2 parents 52e3e2c + e90de3f commit fc2929e

File tree

2 files changed

+134
-3
lines changed

2 files changed

+134
-3
lines changed

pkg/kubelet/kubelet.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,13 +1725,41 @@ func (kl *Kubelet) getPullSecretsForPod(pod *api.Pod) ([]api.Secret, error) {
17251725
return pullSecrets, nil
17261726
}
17271727

1728+
// Return name of a volume. When the volume is a PersistentVolumeClaim,
1729+
// it returns name of the real PersistentVolume bound to the claim.
1730+
// It returns errror when the clam is not bound yet.
1731+
func (kl *Kubelet) resolveVolumeName(pod *api.Pod, volume *api.Volume) (string, error) {
1732+
claimSource := volume.VolumeSource.PersistentVolumeClaim
1733+
if claimSource != nil {
1734+
// resolve real volume behind the claim
1735+
claim, err := kl.kubeClient.Legacy().PersistentVolumeClaims(pod.Namespace).Get(claimSource.ClaimName)
1736+
if err != nil {
1737+
return "", fmt.Errorf("Cannot find claim %s/%s for volume %s", pod.Namespace, claimSource.ClaimName, volume.Name)
1738+
}
1739+
if claim.Status.Phase != api.ClaimBound {
1740+
return "", fmt.Errorf("Claim for volume %s/%s is not bound yet", pod.Namespace, claimSource.ClaimName)
1741+
}
1742+
// Use the real bound volume instead of PersistentVolume.Name
1743+
return claim.Spec.VolumeName, nil
1744+
}
1745+
return volume.Name, nil
1746+
}
1747+
17281748
// Stores all volumes defined by the set of pods into a map.
1749+
// It stores real volumes there, i.e. persistent volume claims are resolved
1750+
// to volumes that are bound to them.
17291751
// Keys for each entry are in the format (POD_ID)/(VOLUME_NAME)
1730-
func getDesiredVolumes(pods []*api.Pod) map[string]api.Volume {
1752+
func (kl *Kubelet) getDesiredVolumes(pods []*api.Pod) map[string]api.Volume {
17311753
desiredVolumes := make(map[string]api.Volume)
17321754
for _, pod := range pods {
17331755
for _, volume := range pod.Spec.Volumes {
1734-
identifier := path.Join(string(pod.UID), volume.Name)
1756+
volumeName, err := kl.resolveVolumeName(pod, &volume)
1757+
if err != nil {
1758+
glog.V(3).Infof("%v", err)
1759+
// Ignore the error and hope it's resolved next time
1760+
continue
1761+
}
1762+
identifier := path.Join(string(pod.UID), volumeName)
17351763
desiredVolumes[identifier] = volume
17361764
}
17371765
}
@@ -1815,8 +1843,11 @@ func (kl *Kubelet) cleanupBandwidthLimits(allPods []*api.Pod) error {
18151843

18161844
// Compares the map of current volumes to the map of desired volumes.
18171845
// If an active volume does not have a respective desired volume, clean it up.
1846+
// This method is blocking:
1847+
// 1) it talks to API server to find volumes bound to persistent volume claims
1848+
// 2) it talks to cloud to detach volumes
18181849
func (kl *Kubelet) cleanupOrphanedVolumes(pods []*api.Pod, runningPods []*kubecontainer.Pod) error {
1819-
desiredVolumes := getDesiredVolumes(pods)
1850+
desiredVolumes := kl.getDesiredVolumes(pods)
18201851
currentVolumes := kl.getPodVolumesFromDisk()
18211852

18221853
runningSet := sets.String{}

pkg/kubelet/kubelet_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,106 @@ func TestGetPodVolumesFromDisk(t *testing.T) {
551551
}
552552
}
553553

554+
// Test for https://github.com/kubernetes/kubernetes/pull/19600
555+
func TestCleanupOrphanedVolumes(t *testing.T) {
556+
testKubelet := newTestKubelet(t)
557+
kubelet := testKubelet.kubelet
558+
kubeClient := testKubelet.fakeKubeClient
559+
plug := &volume.FakeVolumePlugin{PluginName: "fake", Host: nil}
560+
kubelet.volumePluginMgr.InitPlugins([]volume.VolumePlugin{plug}, &volumeHost{kubelet})
561+
562+
// create a volume "on disk"
563+
volsOnDisk := []struct {
564+
podUID types.UID
565+
volName string
566+
}{
567+
{"podUID", "myrealvol"},
568+
}
569+
570+
pathsOnDisk := []string{}
571+
for i := range volsOnDisk {
572+
fv := volume.FakeVolume{PodUID: volsOnDisk[i].podUID, VolName: volsOnDisk[i].volName, Plugin: plug}
573+
fv.SetUp(nil)
574+
pathsOnDisk = append(pathsOnDisk, fv.GetPath())
575+
}
576+
577+
// store the claim in fake kubelet database
578+
claim := api.PersistentVolumeClaim{
579+
ObjectMeta: api.ObjectMeta{
580+
Name: "myclaim",
581+
Namespace: "test",
582+
},
583+
Spec: api.PersistentVolumeClaimSpec{
584+
VolumeName: "myrealvol",
585+
},
586+
Status: api.PersistentVolumeClaimStatus{
587+
Phase: api.ClaimBound,
588+
},
589+
}
590+
kubeClient.ReactionChain = fake.NewSimpleClientset(&api.PersistentVolumeClaimList{Items: []api.PersistentVolumeClaim{
591+
claim,
592+
}}).ReactionChain
593+
594+
// Create a pod referencing the volume via a PersistentVolumeClaim
595+
pod := api.Pod{
596+
ObjectMeta: api.ObjectMeta{
597+
UID: "podUID",
598+
Name: "pod",
599+
Namespace: "test",
600+
},
601+
Spec: api.PodSpec{
602+
Volumes: []api.Volume{
603+
{
604+
Name: "myvolumeclaim",
605+
VolumeSource: api.VolumeSource{
606+
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
607+
ClaimName: "myclaim",
608+
},
609+
},
610+
},
611+
},
612+
},
613+
}
614+
615+
// The pod is pending and not running yet. Test that cleanupOrphanedVolumes
616+
// won't remove the volume from disk if the volume is referenced only
617+
// indirectly by a claim.
618+
err := kubelet.cleanupOrphanedVolumes([]*api.Pod{&pod}, []*kubecontainer.Pod{})
619+
if err != nil {
620+
t.Errorf("cleanupOrphanedVolumes failed: %v", err)
621+
}
622+
623+
volumesFound := kubelet.getPodVolumesFromDisk()
624+
if len(volumesFound) != len(pathsOnDisk) {
625+
t.Errorf("Expected to find %d cleaners, got %d", len(pathsOnDisk), len(volumesFound))
626+
}
627+
for _, ep := range pathsOnDisk {
628+
found := false
629+
for _, cl := range volumesFound {
630+
if ep == cl.GetPath() {
631+
found = true
632+
break
633+
}
634+
}
635+
if !found {
636+
t.Errorf("Could not find a volume with path %s", ep)
637+
}
638+
}
639+
640+
// The pod is deleted -> kubelet should delete the volume
641+
err = kubelet.cleanupOrphanedVolumes([]*api.Pod{}, []*kubecontainer.Pod{})
642+
if err != nil {
643+
t.Errorf("cleanupOrphanedVolumes failed: %v", err)
644+
}
645+
volumesFound = kubelet.getPodVolumesFromDisk()
646+
if len(volumesFound) != 0 {
647+
t.Errorf("Expected to find 0 cleaners, got %d", len(volumesFound))
648+
}
649+
for _, cl := range volumesFound {
650+
t.Errorf("Found unexpected volume %s", cl.GetPath())
651+
}
652+
}
653+
554654
type stubVolume struct {
555655
path string
556656
volume.MetricsNil

0 commit comments

Comments
 (0)