mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-08 11:38:15 +00:00
Remove volume from found during reconstruction if mounted
Add unit tests for removing reconstructed volumes from ASOW
This commit is contained in:
parent
b455270f6e
commit
6d43345c06
@ -228,6 +228,8 @@ type actualStateOfWorld struct {
|
|||||||
// The key in this map is the name of the volume and the value is an object
|
// The key in this map is the name of the volume and the value is an object
|
||||||
// containing more information about the attached volume.
|
// containing more information about the attached volume.
|
||||||
attachedVolumes map[v1.UniqueVolumeName]attachedVolume
|
attachedVolumes map[v1.UniqueVolumeName]attachedVolume
|
||||||
|
// foundDuringReconstruction is a map of volumes which were discovered
|
||||||
|
// from kubelet root directory when kubelet was restarted.
|
||||||
foundDuringReconstruction map[v1.UniqueVolumeName]map[volumetypes.UniquePodName]types.UID
|
foundDuringReconstruction map[v1.UniqueVolumeName]map[volumetypes.UniquePodName]types.UID
|
||||||
|
|
||||||
// volumePluginMgr is the volume plugin manager used to create volume
|
// volumePluginMgr is the volume plugin manager used to create volume
|
||||||
@ -366,14 +368,15 @@ func (asw *actualStateOfWorld) AddVolumeViaReconstruction(opts operationexecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (asw *actualStateOfWorld) IsVolumeReconstructed(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
func (asw *actualStateOfWorld) IsVolumeReconstructed(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
||||||
asw.RLock()
|
|
||||||
defer asw.RUnlock()
|
|
||||||
|
|
||||||
volumeState := asw.GetVolumeMountState(volumeName, podName)
|
volumeState := asw.GetVolumeMountState(volumeName, podName)
|
||||||
|
|
||||||
// only uncertain volumes are reconstructed
|
// only uncertain volumes are reconstructed
|
||||||
if volumeState != operationexecutor.VolumeMountUncertain {
|
if volumeState != operationexecutor.VolumeMountUncertain {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asw.RLock()
|
||||||
|
defer asw.RUnlock()
|
||||||
podMap, ok := asw.foundDuringReconstruction[volumeName]
|
podMap, ok := asw.foundDuringReconstruction[volumeName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
@ -571,6 +574,11 @@ func (asw *actualStateOfWorld) AddPodToVolume(markVolumeOpts operationexecutor.M
|
|||||||
// If pod exists, reset remountRequired value
|
// If pod exists, reset remountRequired value
|
||||||
podObj.remountRequired = false
|
podObj.remountRequired = false
|
||||||
podObj.volumeMountStateForPod = markVolumeOpts.VolumeMountState
|
podObj.volumeMountStateForPod = markVolumeOpts.VolumeMountState
|
||||||
|
|
||||||
|
// if volume is mounted successfully, then it should be removed from foundDuringReconstruction map
|
||||||
|
if markVolumeOpts.VolumeMountState == operationexecutor.VolumeMounted {
|
||||||
|
delete(asw.foundDuringReconstruction[volumeName], podName)
|
||||||
|
}
|
||||||
if mounter != nil {
|
if mounter != nil {
|
||||||
// The mounter stored in the object may have old information,
|
// The mounter stored in the object may have old information,
|
||||||
// use the newest one.
|
// use the newest one.
|
||||||
|
@ -17,7 +17,9 @@ limitations under the License.
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -458,6 +460,149 @@ func Test_AddTwoPodsToVolume_Positive(t *testing.T) {
|
|||||||
verifyVolumeMountedElsewhere(t, podName2, generatedVolumeName2, true /*expectedMountedElsewhere */, asw)
|
verifyVolumeMountedElsewhere(t, podName2, generatedVolumeName2, true /*expectedMountedElsewhere */, asw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActualStateOfWorld_FoundDuringReconstruction(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
opCallback func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error
|
||||||
|
verifyCallback func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "marking volume mounted should remove volume from found during reconstruction",
|
||||||
|
opCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
|
||||||
|
volumeOpts.VolumeMountState = operationexecutor.VolumeMounted
|
||||||
|
return asw.MarkVolumeAsMounted(volumeOpts)
|
||||||
|
},
|
||||||
|
verifyCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
|
||||||
|
ok := asw.IsVolumeReconstructed(volumeOpts.VolumeName, volumeOpts.PodName)
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("found unexpected volume in reconstructed volume list")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "removing volume from pod should remove volume from found during reconstruction",
|
||||||
|
opCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
|
||||||
|
return asw.MarkVolumeAsUnmounted(volumeOpts.PodName, volumeOpts.VolumeName)
|
||||||
|
},
|
||||||
|
verifyCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
|
||||||
|
ok := asw.IsVolumeReconstructed(volumeOpts.VolumeName, volumeOpts.PodName)
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("found unexpected volume in reconstructed volume list")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "removing volume entirely from ASOW should remove volume from found during reconstruction",
|
||||||
|
opCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
|
||||||
|
err := asw.MarkVolumeAsUnmounted(volumeOpts.PodName, volumeOpts.VolumeName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
asw.MarkVolumeAsDetached(volumeOpts.VolumeName, "")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
verifyCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
|
||||||
|
ok := asw.IsVolumeReconstructed(volumeOpts.VolumeName, volumeOpts.PodName)
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("found unexpected volume in reconstructed volume list")
|
||||||
|
}
|
||||||
|
aswInstance, _ := asw.(*actualStateOfWorld)
|
||||||
|
_, found := aswInstance.foundDuringReconstruction[volumeOpts.VolumeName]
|
||||||
|
if found {
|
||||||
|
return fmt.Errorf("found unexpected volume in reconstructed map")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
volumePluginMgr, plugin := volumetesting.GetTestKubeletVolumePluginMgr(t)
|
||||||
|
asw := NewActualStateOfWorld("mynode" /* nodeName */, volumePluginMgr)
|
||||||
|
devicePath := "fake/device/path"
|
||||||
|
|
||||||
|
pod1 := getTestPod("pod1", "pod1uid", "volume-name-1", "fake-device1")
|
||||||
|
volumeSpec1 := &volume.Spec{Volume: &pod1.Spec.Volumes[0]}
|
||||||
|
generatedVolumeName1, err := util.GetUniqueVolumeNameFromSpec(
|
||||||
|
plugin, volumeSpec1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = asw.MarkVolumeAsAttached(generatedVolumeName1, volumeSpec1, "" /* nodeName */, devicePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MarkVolumeAsAttached failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
podName1 := util.GetUniquePodName(pod1)
|
||||||
|
|
||||||
|
mounter1, err := plugin.NewMounter(volumeSpec1, pod1, volume.VolumeOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewMounter failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper1, err := plugin.NewBlockVolumeMapper(volumeSpec1, pod1, volume.VolumeOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewBlockVolumeMapper failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
markVolumeOpts1 := operationexecutor.MarkVolumeOpts{
|
||||||
|
PodName: podName1,
|
||||||
|
PodUID: pod1.UID,
|
||||||
|
VolumeName: generatedVolumeName1,
|
||||||
|
Mounter: mounter1,
|
||||||
|
BlockVolumeMapper: mapper1,
|
||||||
|
OuterVolumeSpecName: volumeSpec1.Name(),
|
||||||
|
VolumeSpec: volumeSpec1,
|
||||||
|
VolumeMountState: operationexecutor.VolumeMountUncertain,
|
||||||
|
}
|
||||||
|
err = asw.AddVolumeViaReconstruction(markVolumeOpts1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
// make sure state is as we expect it to be
|
||||||
|
verifyVolumeExistsAsw(t, generatedVolumeName1, true /* shouldExist */, asw)
|
||||||
|
verifyVolumeDoesntExistInUnmountedVolumes(t, generatedVolumeName1, asw)
|
||||||
|
verifyVolumeDoesntExistInGloballyMountedVolumes(t, generatedVolumeName1, asw)
|
||||||
|
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName1, volumeSpec1.Name(), asw)
|
||||||
|
verifyVolumeSpecNameInVolumeAsw(t, podName1, []*volume.Spec{volumeSpec1}, asw)
|
||||||
|
verifyVolumeFoundInReconstruction(t, podName1, generatedVolumeName1, asw)
|
||||||
|
|
||||||
|
if tc.opCallback != nil {
|
||||||
|
err = tc.opCallback(asw, markVolumeOpts1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("for test %s: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tc.verifyCallback(asw, markVolumeOpts1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("for test %s verification failed: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestPod(podName, podUID, outerVolumeName, pdName string) *v1.Pod {
|
||||||
|
pod := &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: podName,
|
||||||
|
UID: types.UID(podUID),
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Volumes: []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: outerVolumeName,
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
|
||||||
|
PDName: pdName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return pod
|
||||||
|
}
|
||||||
|
|
||||||
// Calls AddPodToVolume() to add pod to empty data struct
|
// Calls AddPodToVolume() to add pod to empty data struct
|
||||||
// Verifies call fails with "volume does not exist" error.
|
// Verifies call fails with "volume does not exist" error.
|
||||||
func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) {
|
func Test_AddPodToVolume_Negative_VolumeDoesntExist(t *testing.T) {
|
||||||
@ -873,3 +1018,10 @@ func verifyVolumeSpecNameInVolumeAsw(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyVolumeFoundInReconstruction(t *testing.T, podToCheck volumetypes.UniquePodName, volumeToCheck v1.UniqueVolumeName, asw ActualStateOfWorld) {
|
||||||
|
isRecontructed := asw.IsVolumeReconstructed(volumeToCheck, podToCheck)
|
||||||
|
if !isRecontructed {
|
||||||
|
t.Fatalf("ASW IsVolumeReconstructed result invalid. expected <true> Actual <false>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -178,6 +178,10 @@ func (rc *reconciler) reconcile() {
|
|||||||
// Ensure devices that should be detached/unmounted are detached/unmounted.
|
// Ensure devices that should be detached/unmounted are detached/unmounted.
|
||||||
rc.unmountDetachDevices()
|
rc.unmountDetachDevices()
|
||||||
|
|
||||||
|
// After running the above operations if skippedDuringReconstruction is not empty
|
||||||
|
// then ensure that all volumes which were discovered and skipped during reconstruction
|
||||||
|
// are added to actualStateOfWorld in uncertain state.
|
||||||
|
// This should be called only ONCE after reconstruction.
|
||||||
if len(rc.skippedDuringReconstruction) > 0 {
|
if len(rc.skippedDuringReconstruction) > 0 {
|
||||||
rc.processReconstructedVolumes()
|
rc.processReconstructedVolumes()
|
||||||
}
|
}
|
||||||
@ -279,7 +283,18 @@ func (rc *reconciler) processReconstructedVolumes() {
|
|||||||
volumeNotMounted := rc.actualStateOfWorld.PodRemovedFromVolume(podName, volume.volumeName)
|
volumeNotMounted := rc.actualStateOfWorld.PodRemovedFromVolume(podName, volume.volumeName)
|
||||||
// if volume is not mounted then lets mark volume mounted in uncertain state in ASOW
|
// if volume is not mounted then lets mark volume mounted in uncertain state in ASOW
|
||||||
if volumeNotMounted {
|
if volumeNotMounted {
|
||||||
err := rc.markVolumeState(volume, operationexecutor.VolumeMountUncertain)
|
markVolumeOpts := operationexecutor.MarkVolumeOpts{
|
||||||
|
PodName: volume.podName,
|
||||||
|
PodUID: types.UID(volume.podName),
|
||||||
|
VolumeName: volume.volumeName,
|
||||||
|
Mounter: volume.mounter,
|
||||||
|
BlockVolumeMapper: volume.blockVolumeMapper,
|
||||||
|
OuterVolumeSpecName: volume.outerVolumeSpecName,
|
||||||
|
VolumeGidVolume: volume.volumeGidValue,
|
||||||
|
VolumeSpec: volume.volumeSpec,
|
||||||
|
VolumeMountState: operationexecutor.VolumeMountUncertain,
|
||||||
|
}
|
||||||
|
err := rc.actualStateOfWorld.AddVolumeViaReconstruction(markVolumeOpts)
|
||||||
uncertainVolumeCount += 1
|
uncertainVolumeCount += 1
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.ErrorS(err, "Could not add pod to volume information to actual state of world", "pod", klog.KObj(volume.pod))
|
klog.ErrorS(err, "Could not add pod to volume information to actual state of world", "pod", klog.KObj(volume.pod))
|
||||||
@ -429,6 +444,8 @@ type reconstructedVolume struct {
|
|||||||
blockVolumeMapper volumepkg.BlockVolumeMapper
|
blockVolumeMapper volumepkg.BlockVolumeMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// globalVolumeInfo stores reconstructed volume information
|
||||||
|
// for each pod that was using that volume.
|
||||||
type globalVolumeInfo struct {
|
type globalVolumeInfo struct {
|
||||||
volumeName v1.UniqueVolumeName
|
volumeName v1.UniqueVolumeName
|
||||||
volumeSpec *volumepkg.Spec
|
volumeSpec *volumepkg.Spec
|
||||||
|
@ -19,14 +19,15 @@ package reconciler
|
|||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
csitrans "k8s.io/csi-translation-lib"
|
|
||||||
"k8s.io/kubernetes/pkg/volume/csimigration"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
csitrans "k8s.io/csi-translation-lib"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/csimigration"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"k8s.io/mount-utils"
|
"k8s.io/mount-utils"
|
||||||
|
|
||||||
@ -2297,6 +2298,11 @@ func TestSyncStates(t *testing.T) {
|
|||||||
if len(mountedPods) != 1 {
|
if len(mountedPods) != 1 {
|
||||||
return fmt.Errorf("expected 1 pods to in mounted volume list got %d", len(mountedPods))
|
return fmt.Errorf("expected 1 pods to in mounted volume list got %d", len(mountedPods))
|
||||||
}
|
}
|
||||||
|
mountedPodVolume := mountedPods[0]
|
||||||
|
addedViaReconstruction := rcInstance.actualStateOfWorld.IsVolumeReconstructed(mountedPodVolume.VolumeName, mountedPodVolume.PodName)
|
||||||
|
if !addedViaReconstruction {
|
||||||
|
return fmt.Errorf("expected volume %s to be marked as added via reconstruction", mountedPodVolume.VolumeName)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user