Merge pull request #117804 from jsafrane/fix-csi-attachable-reconstruction

Fix reconstruction of CSI volumes
This commit is contained in:
Kubernetes Prow Robot 2023-07-12 10:57:15 -07:00 committed by GitHub
commit ac07b4612e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 337 additions and 57 deletions

View File

@ -169,14 +169,20 @@ type ActualStateOfWorld interface {
GetAttachedVolumes() []AttachedVolume GetAttachedVolumes() []AttachedVolume
// SyncReconstructedVolume check the volume.outerVolumeSpecName in asw and // SyncReconstructedVolume check the volume.outerVolumeSpecName in asw and
// the one populated from dsw , if they do not match, update this field from the value from dsw. // the one populated from dsw, if they do not match, update this field from the value from dsw.
SyncReconstructedVolume(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName, outerVolumeSpecName string) SyncReconstructedVolume(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName, outerVolumeSpecName string)
// Add the specified volume to ASW as uncertainly attached.
AddAttachUncertainReconstructedVolume(volumeName v1.UniqueVolumeName, volumeSpec *volume.Spec, nodeName types.NodeName, devicePath string) error
// UpdateReconstructedDevicePath updates devicePath of a reconstructed volume // UpdateReconstructedDevicePath updates devicePath of a reconstructed volume
// from Node.Status.VolumesAttached. The ASW is updated only when the volume is still // from Node.Status.VolumesAttached. The ASW is updated only when the volume is still
// uncertain. If the volume got mounted in the meantime, its devicePath must have // uncertain. If the volume got mounted in the meantime, its devicePath must have
// been fixed by such an update. // been fixed by such an update.
UpdateReconstructedDevicePath(volumeName v1.UniqueVolumeName, devicePath string) UpdateReconstructedDevicePath(volumeName v1.UniqueVolumeName, devicePath string)
// UpdateReconstructedVolumeAttachability updates volume attachability from the API server.
UpdateReconstructedVolumeAttachability(volumeName v1.UniqueVolumeName, volumeAttachable bool)
} }
// MountedVolume represents a volume that has successfully been mounted to a pod. // MountedVolume represents a volume that has successfully been mounted to a pod.
@ -251,6 +257,14 @@ type actualStateOfWorld struct {
sync.RWMutex sync.RWMutex
} }
type volumeAttachability string
const (
volumeAttachabilityTrue volumeAttachability = "True"
volumeAttachabilityFalse volumeAttachability = "False"
volumeAttachabilityUncertain volumeAttachability = "Uncertain"
)
// attachedVolume represents a volume the kubelet volume manager believes to be // attachedVolume represents a volume the kubelet volume manager believes to be
// successfully attached to a node it is managing. Volume types that do not // successfully attached to a node it is managing. Volume types that do not
// implement an attacher are assumed to be in this state. // implement an attacher are assumed to be in this state.
@ -280,7 +294,7 @@ type attachedVolume struct {
// pluginIsAttachable indicates the volume plugin used to attach and mount // pluginIsAttachable indicates the volume plugin used to attach and mount
// this volume implements the volume.Attacher interface // this volume implements the volume.Attacher interface
pluginIsAttachable bool pluginIsAttachable volumeAttachability
// deviceMountState stores information that tells us if device is mounted // deviceMountState stores information that tells us if device is mounted
// globally or not // globally or not
@ -361,7 +375,19 @@ type mountedPod struct {
func (asw *actualStateOfWorld) MarkVolumeAsAttached( func (asw *actualStateOfWorld) MarkVolumeAsAttached(
logger klog.Logger, logger klog.Logger,
volumeName v1.UniqueVolumeName, volumeSpec *volume.Spec, _ types.NodeName, devicePath string) error { volumeName v1.UniqueVolumeName, volumeSpec *volume.Spec, _ types.NodeName, devicePath string) error {
return asw.addVolume(volumeName, volumeSpec, devicePath)
pluginIsAttachable := volumeAttachabilityFalse
if attachablePlugin, err := asw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec); err == nil && attachablePlugin != nil {
pluginIsAttachable = volumeAttachabilityTrue
}
return asw.addVolume(volumeName, volumeSpec, devicePath, pluginIsAttachable)
}
func (asw *actualStateOfWorld) AddAttachUncertainReconstructedVolume(
volumeName v1.UniqueVolumeName, volumeSpec *volume.Spec, _ types.NodeName, devicePath string) error {
return asw.addVolume(volumeName, volumeSpec, devicePath, volumeAttachabilityUncertain)
} }
func (asw *actualStateOfWorld) MarkVolumeAsUncertain( func (asw *actualStateOfWorld) MarkVolumeAsUncertain(
@ -526,6 +552,28 @@ func (asw *actualStateOfWorld) UpdateReconstructedDevicePath(volumeName v1.Uniqu
asw.attachedVolumes[volumeName] = volumeObj asw.attachedVolumes[volumeName] = volumeObj
} }
func (asw *actualStateOfWorld) UpdateReconstructedVolumeAttachability(volumeName v1.UniqueVolumeName, attachable bool) {
asw.Lock()
defer asw.Unlock()
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
if !volumeExists {
return
}
if volumeObj.pluginIsAttachable != volumeAttachabilityUncertain {
// Reconciler must have updated volume state, i.e. when a pod uses the volume and
// succeeded mounting the volume. Such update has fixed the device path.
return
}
if attachable {
volumeObj.pluginIsAttachable = volumeAttachabilityTrue
} else {
volumeObj.pluginIsAttachable = volumeAttachabilityFalse
}
asw.attachedVolumes[volumeName] = volumeObj
}
func (asw *actualStateOfWorld) GetDeviceMountState(volumeName v1.UniqueVolumeName) operationexecutor.DeviceMountState { func (asw *actualStateOfWorld) GetDeviceMountState(volumeName v1.UniqueVolumeName) operationexecutor.DeviceMountState {
asw.RLock() asw.RLock()
defer asw.RUnlock() defer asw.RUnlock()
@ -592,7 +640,7 @@ func (asw *actualStateOfWorld) IsVolumeMountedElsewhere(volumeName v1.UniqueVolu
// volume plugin can support the given volumeSpec or more than one plugin can // volume plugin can support the given volumeSpec or more than one plugin can
// support it, an error is returned. // support it, an error is returned.
func (asw *actualStateOfWorld) addVolume( func (asw *actualStateOfWorld) addVolume(
volumeName v1.UniqueVolumeName, volumeSpec *volume.Spec, devicePath string) error { volumeName v1.UniqueVolumeName, volumeSpec *volume.Spec, devicePath string, attachability volumeAttachability) error {
asw.Lock() asw.Lock()
defer asw.Unlock() defer asw.Unlock()
@ -615,11 +663,6 @@ func (asw *actualStateOfWorld) addVolume(
} }
} }
pluginIsAttachable := false
if attachablePlugin, err := asw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec); err == nil && attachablePlugin != nil {
pluginIsAttachable = true
}
volumeObj, volumeExists := asw.attachedVolumes[volumeName] volumeObj, volumeExists := asw.attachedVolumes[volumeName]
if !volumeExists { if !volumeExists {
volumeObj = attachedVolume{ volumeObj = attachedVolume{
@ -627,7 +670,7 @@ func (asw *actualStateOfWorld) addVolume(
spec: volumeSpec, spec: volumeSpec,
mountedPods: make(map[volumetypes.UniquePodName]mountedPod), mountedPods: make(map[volumetypes.UniquePodName]mountedPod),
pluginName: volumePlugin.GetPluginName(), pluginName: volumePlugin.GetPluginName(),
pluginIsAttachable: pluginIsAttachable, pluginIsAttachable: attachability,
deviceMountState: operationexecutor.DeviceNotMounted, deviceMountState: operationexecutor.DeviceNotMounted,
devicePath: devicePath, devicePath: devicePath,
} }
@ -1094,7 +1137,7 @@ func (asw *actualStateOfWorld) newAttachedVolume(
VolumeName: attachedVolume.volumeName, VolumeName: attachedVolume.volumeName,
VolumeSpec: attachedVolume.spec, VolumeSpec: attachedVolume.spec,
NodeName: asw.nodeName, NodeName: asw.nodeName,
PluginIsAttachable: attachedVolume.pluginIsAttachable, PluginIsAttachable: attachedVolume.pluginIsAttachable == volumeAttachabilityTrue,
DevicePath: attachedVolume.devicePath, DevicePath: attachedVolume.devicePath,
DeviceMountPath: attachedVolume.deviceMountPath, DeviceMountPath: attachedVolume.deviceMountPath,
PluginName: attachedVolume.pluginName, PluginName: attachedVolume.pluginName,

View File

@ -525,6 +525,54 @@ func TestActualStateOfWorld_FoundDuringReconstruction(t *testing.T) {
return nil return nil
}, },
}, },
{
name: "uncertain attachability is resolved to attachable",
opCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
asw.UpdateReconstructedVolumeAttachability(volumeOpts.VolumeName, true)
return nil
},
verifyCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
verifyVolumeAttachability(t, volumeOpts.VolumeName, asw, volumeAttachabilityTrue)
return nil
},
},
{
name: "uncertain attachability is resolved to non-attachable",
opCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
asw.UpdateReconstructedVolumeAttachability(volumeOpts.VolumeName, false)
return nil
},
verifyCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
verifyVolumeAttachability(t, volumeOpts.VolumeName, asw, volumeAttachabilityFalse)
return nil
},
},
{
name: "certain (false) attachability cannot be changed",
opCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
asw.UpdateReconstructedVolumeAttachability(volumeOpts.VolumeName, false)
// This function should be NOOP:
asw.UpdateReconstructedVolumeAttachability(volumeOpts.VolumeName, true)
return nil
},
verifyCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
verifyVolumeAttachability(t, volumeOpts.VolumeName, asw, volumeAttachabilityFalse)
return nil
},
},
{
name: "certain (true) attachability cannot be changed",
opCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
asw.UpdateReconstructedVolumeAttachability(volumeOpts.VolumeName, true)
// This function should be NOOP:
asw.UpdateReconstructedVolumeAttachability(volumeOpts.VolumeName, false)
return nil
},
verifyCallback: func(asw ActualStateOfWorld, volumeOpts operationexecutor.MarkVolumeOpts) error {
verifyVolumeAttachability(t, volumeOpts.VolumeName, asw, volumeAttachabilityTrue)
return nil
},
},
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
@ -537,8 +585,7 @@ func TestActualStateOfWorld_FoundDuringReconstruction(t *testing.T) {
generatedVolumeName1, err := util.GetUniqueVolumeNameFromSpec( generatedVolumeName1, err := util.GetUniqueVolumeNameFromSpec(
plugin, volumeSpec1) plugin, volumeSpec1)
require.NoError(t, err) require.NoError(t, err)
logger, _ := ktesting.NewTestContext(t) err = asw.AddAttachUncertainReconstructedVolume(generatedVolumeName1, volumeSpec1, "" /* nodeName */, devicePath)
err = asw.MarkVolumeAsAttached(logger, generatedVolumeName1, volumeSpec1, "" /* nodeName */, devicePath)
if err != nil { if err != nil {
t.Fatalf("MarkVolumeAsAttached failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("MarkVolumeAsAttached failed. Expected: <no error> Actual: <%v>", err)
} }
@ -575,6 +622,7 @@ func TestActualStateOfWorld_FoundDuringReconstruction(t *testing.T) {
verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName1, volumeSpec1.Name(), asw) verifyVolumeExistsWithSpecNameInVolumeAsw(t, podName1, volumeSpec1.Name(), asw)
verifyVolumeSpecNameInVolumeAsw(t, podName1, []*volume.Spec{volumeSpec1}, asw) verifyVolumeSpecNameInVolumeAsw(t, podName1, []*volume.Spec{volumeSpec1}, asw)
verifyVolumeFoundInReconstruction(t, podName1, generatedVolumeName1, asw) verifyVolumeFoundInReconstruction(t, podName1, generatedVolumeName1, asw)
verifyVolumeAttachability(t, generatedVolumeName1, asw, volumeAttachabilityUncertain)
if tc.opCallback != nil { if tc.opCallback != nil {
err = tc.opCallback(asw, markVolumeOpts1) err = tc.opCallback(asw, markVolumeOpts1)
@ -1307,3 +1355,28 @@ func verifyVolumeFoundInReconstruction(t *testing.T, podToCheck volumetypes.Uniq
t.Fatalf("ASW IsVolumeReconstructed result invalid. expected <true> Actual <false>") t.Fatalf("ASW IsVolumeReconstructed result invalid. expected <true> Actual <false>")
} }
} }
func verifyVolumeAttachability(t *testing.T, volumeToCheck v1.UniqueVolumeName, asw ActualStateOfWorld, expected volumeAttachability) {
attached := asw.GetAttachedVolumes()
attachable := false
for _, volume := range attached {
if volume.VolumeName == volumeToCheck {
attachable = volume.PluginIsAttachable
break
}
}
switch expected {
case volumeAttachabilityTrue:
if !attachable {
t.Errorf("ASW reports %s as not-attachable, when %s was expected", volumeToCheck, expected)
}
// ASW does not have any special difference between False and Uncertain.
// Uncertain only allows to be changed to True / False.
case volumeAttachabilityUncertain, volumeAttachabilityFalse:
if attachable {
t.Errorf("ASW reports %s as attachable, when %s was expected", volumeToCheck, expected)
}
}
}

View File

@ -120,7 +120,7 @@ func NewReconciler(
kubeletPodsDir: kubeletPodsDir, kubeletPodsDir: kubeletPodsDir,
timeOfLastSync: time.Time{}, timeOfLastSync: time.Time{},
volumesFailedReconstruction: make([]podVolume, 0), volumesFailedReconstruction: make([]podVolume, 0),
volumesNeedDevicePath: make([]v1.UniqueVolumeName, 0), volumesNeedUpdateFromNodeStatus: make([]v1.UniqueVolumeName, 0),
volumesNeedReportedInUse: make([]v1.UniqueVolumeName, 0), volumesNeedReportedInUse: make([]v1.UniqueVolumeName, 0),
} }
} }
@ -144,7 +144,7 @@ type reconciler struct {
timeOfLastSyncLock sync.Mutex timeOfLastSyncLock sync.Mutex
timeOfLastSync time.Time timeOfLastSync time.Time
volumesFailedReconstruction []podVolume volumesFailedReconstruction []podVolume
volumesNeedDevicePath []v1.UniqueVolumeName volumesNeedUpdateFromNodeStatus []v1.UniqueVolumeName
volumesNeedReportedInUse []v1.UniqueVolumeName volumesNeedReportedInUse []v1.UniqueVolumeName
} }

View File

@ -56,8 +56,14 @@ func (rc *reconciler) reconcileNew() {
rc.cleanOrphanVolumes() rc.cleanOrphanVolumes()
} }
if len(rc.volumesNeedDevicePath) != 0 { if len(rc.volumesNeedUpdateFromNodeStatus) != 0 {
rc.updateReconstructedDevicePaths() rc.updateReconstructedFromNodeStatus()
}
if len(rc.volumesNeedUpdateFromNodeStatus) == 0 {
// ASW is fully populated only after both devicePaths and uncertain volume attach-ability
// were reconstructed from the API server.
// This will start reconciliation of node.status.volumesInUse.
rc.updateLastSyncTime()
} }
if len(rc.volumesNeedReportedInUse) != 0 && rc.populatorHasAddedPods() { if len(rc.volumesNeedReportedInUse) != 0 && rc.populatorHasAddedPods() {

View File

@ -0,0 +1,144 @@
/*
Copyright 2023 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 reconciler
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/kubelet/volumemanager/cache"
"k8s.io/kubernetes/pkg/volume"
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
"k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util/hostutil"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
"k8s.io/mount-utils"
)
func TestReconcileWithUpdateReconstructedFromAPIServer(t *testing.T) {
// Calls Run() with two reconstructed volumes.
// Verifies the devicePaths + volume attachability are reconstructed from node.status.
// Arrange
node := &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: string(nodeName),
},
Status: v1.NodeStatus{
VolumesAttached: []v1.AttachedVolume{
{
Name: "fake-plugin/fake-device1",
DevicePath: "fake/path",
},
},
},
}
volumePluginMgr, fakePlugin := volumetesting.GetTestKubeletVolumePluginMgrWithNode(t, node)
seLinuxTranslator := util.NewFakeSELinuxLabelTranslator()
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr, seLinuxTranslator)
asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
kubeClient := createTestClient()
fakeRecorder := &record.FakeRecorder{}
fakeHandler := volumetesting.NewBlockVolumePathHandler()
oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
kubeClient,
volumePluginMgr,
fakeRecorder,
fakeHandler))
rc := NewReconciler(
kubeClient,
true, /* controllerAttachDetachEnabled */
reconcilerLoopSleepDuration,
waitForAttachTimeout,
nodeName,
dsw,
asw,
hasAddedPods,
oex,
mount.NewFakeMounter(nil),
hostutil.NewFakeHostUtil(nil),
volumePluginMgr,
kubeletPodsDir)
reconciler := rc.(*reconciler)
// The pod has two volumes, fake-device1 is attachable, fake-device2 is not.
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod1",
UID: "pod1uid",
},
Spec: v1.PodSpec{
Volumes: []v1.Volume{
{
Name: "volume-name",
VolumeSource: v1.VolumeSource{
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
PDName: "fake-device1",
},
},
},
{
Name: "volume-name2",
VolumeSource: v1.VolumeSource{
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
PDName: "fake-device2",
},
},
},
},
},
}
volumeSpec1 := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
volumeName1 := util.GetUniqueVolumeName(fakePlugin.GetPluginName(), "fake-device1")
volumeSpec2 := &volume.Spec{Volume: &pod.Spec.Volumes[1]}
volumeName2 := util.GetUniqueVolumeName(fakePlugin.GetPluginName(), "fake-device2")
assert.NoError(t, asw.AddAttachUncertainReconstructedVolume(volumeName1, volumeSpec1, nodeName, ""))
assert.NoError(t, asw.MarkDeviceAsUncertain(volumeName1, "/dev/badly/reconstructed", "/var/lib/kubelet/plugins/global1", ""))
assert.NoError(t, asw.AddAttachUncertainReconstructedVolume(volumeName2, volumeSpec2, nodeName, ""))
assert.NoError(t, asw.MarkDeviceAsUncertain(volumeName2, "/dev/reconstructed", "/var/lib/kubelet/plugins/global2", ""))
assert.False(t, reconciler.StatesHasBeenSynced())
reconciler.volumesNeedUpdateFromNodeStatus = append(reconciler.volumesNeedUpdateFromNodeStatus, volumeName1, volumeName2)
// Act - run reconcile loop just once.
// "volumesNeedUpdateFromNodeStatus" is not empty, so no unmount will be triggered.
reconciler.reconcileNew()
// Assert
assert.True(t, reconciler.StatesHasBeenSynced())
assert.Empty(t, reconciler.volumesNeedUpdateFromNodeStatus)
attachedVolumes := asw.GetAttachedVolumes()
assert.Equalf(t, len(attachedVolumes), 2, "two volumes in ASW expected")
for _, vol := range attachedVolumes {
if vol.VolumeName == volumeName1 {
// devicePath + attachability must have been updated from node.status
assert.True(t, vol.PluginIsAttachable)
assert.Equal(t, vol.DevicePath, "fake/path")
}
if vol.VolumeName == volumeName2 {
// only attachability was updated from node.status
assert.False(t, vol.PluginIsAttachable)
assert.Equal(t, vol.DevicePath, "/dev/reconstructed")
}
}
}

View File

@ -236,17 +236,28 @@ func (rc *reconciler) reconstructVolume(volume podVolume) (rvolume *reconstructe
// Searching by spec checks whether the volume is actually attachable // Searching by spec checks whether the volume is actually attachable
// (i.e. has a PV) whereas searching by plugin name can only tell whether // (i.e. has a PV) whereas searching by plugin name can only tell whether
// the plugin supports attachable volumes. // the plugin supports attachable volumes.
attachablePlugin, err := rc.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
if err != nil {
return nil, err
}
deviceMountablePlugin, err := rc.volumePluginMgr.FindDeviceMountablePluginBySpec(volumeSpec) deviceMountablePlugin, err := rc.volumePluginMgr.FindDeviceMountablePluginBySpec(volumeSpec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// The unique volume name used depends on whether the volume is attachable/device-mountable
// (needsNameFromSpec = true) or not.
needsNameFromSpec := deviceMountablePlugin != nil
if !needsNameFromSpec {
// Check attach-ability of a volume only as a fallback to avoid calling
// FindAttachablePluginBySpec for CSI volumes - it needs a connection to the API server,
// but it may not be available at this stage of kubelet startup.
// All CSI volumes are device-mountable, so they won't reach this code.
attachablePlugin, err := rc.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
if err != nil {
return nil, err
}
needsNameFromSpec = attachablePlugin != nil
}
var uniqueVolumeName v1.UniqueVolumeName var uniqueVolumeName v1.UniqueVolumeName
if attachablePlugin != nil || deviceMountablePlugin != nil { if needsNameFromSpec {
uniqueVolumeName, err = util.GetUniqueVolumeNameFromSpec(plugin, volumeSpec) uniqueVolumeName, err = util.GetUniqueVolumeNameFromSpec(plugin, volumeSpec)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -39,7 +39,7 @@ func (rc *reconciler) readyToUnmount() bool {
// Allow unmount only when ASW device paths were corrected from node.status to prevent // Allow unmount only when ASW device paths were corrected from node.status to prevent
// calling unmount with a wrong devicePath. // calling unmount with a wrong devicePath.
if len(rc.volumesNeedDevicePath) != 0 { if len(rc.volumesNeedUpdateFromNodeStatus) != 0 {
return false return false
} }
return true return true
@ -50,7 +50,6 @@ func (rc *reconciler) readyToUnmount() bool {
// put the volumes to volumesFailedReconstruction to be cleaned up later when DesiredStateOfWorld // put the volumes to volumesFailedReconstruction to be cleaned up later when DesiredStateOfWorld
// is populated. // is populated.
func (rc *reconciler) reconstructVolumes() { func (rc *reconciler) reconstructVolumes() {
defer rc.updateLastSyncTime()
// Get volumes information by reading the pod's directory // Get volumes information by reading the pod's directory
podVolumes, err := getVolumesFromPodDir(rc.kubeletPodsDir) podVolumes, err := getVolumesFromPodDir(rc.kubeletPodsDir)
if err != nil { if err != nil {
@ -98,16 +97,16 @@ func (rc *reconciler) reconstructVolumes() {
// Remember to update DSW with this information. // Remember to update DSW with this information.
rc.volumesNeedReportedInUse = reconstructedVolumeNames rc.volumesNeedReportedInUse = reconstructedVolumeNames
// Remember to update devicePath from node.status.volumesAttached // Remember to update devicePath from node.status.volumesAttached
rc.volumesNeedDevicePath = reconstructedVolumeNames rc.volumesNeedUpdateFromNodeStatus = reconstructedVolumeNames
} }
klog.V(2).InfoS("Volume reconstruction finished") klog.V(2).InfoS("Volume reconstruction finished")
} }
func (rc *reconciler) updateStatesNew(reconstructedVolumes map[v1.UniqueVolumeName]*globalVolumeInfo) { func (rc *reconciler) updateStatesNew(reconstructedVolumes map[v1.UniqueVolumeName]*globalVolumeInfo) {
for _, gvl := range reconstructedVolumes { for _, gvl := range reconstructedVolumes {
err := rc.actualStateOfWorld.MarkVolumeAsAttached( err := rc.actualStateOfWorld.AddAttachUncertainReconstructedVolume(
//TODO: the devicePath might not be correct for some volume plugins: see issue #54108 //TODO: the devicePath might not be correct for some volume plugins: see issue #54108
klog.TODO(), gvl.volumeName, gvl.volumeSpec, rc.nodeName, gvl.devicePath) gvl.volumeName, gvl.volumeSpec, rc.nodeName, gvl.devicePath)
if err != nil { if err != nil {
klog.ErrorS(err, "Could not add volume information to actual state of world", "volumeName", gvl.volumeName) klog.ErrorS(err, "Could not add volume information to actual state of world", "volumeName", gvl.volumeName)
continue continue
@ -174,36 +173,40 @@ func (rc *reconciler) cleanOrphanVolumes() {
rc.volumesFailedReconstruction = make([]podVolume, 0) rc.volumesFailedReconstruction = make([]podVolume, 0)
} }
// updateReconstructedDevicePaths tries to file devicePaths of reconstructed volumes from // updateReconstructedFromNodeStatus tries to file devicePaths of reconstructed volumes from
// node.Status.VolumesAttached. This can be done only after connection to the API // node.Status.VolumesAttached. This can be done only after connection to the API
// server is established, i.e. it can't be part of reconstructVolumes(). // server is established, i.e. it can't be part of reconstructVolumes().
func (rc *reconciler) updateReconstructedDevicePaths() { func (rc *reconciler) updateReconstructedFromNodeStatus() {
klog.V(4).InfoS("Updating reconstructed devicePaths") klog.V(4).InfoS("Updating reconstructed devicePaths")
if rc.kubeClient == nil { if rc.kubeClient == nil {
// Skip reconstructing devicePath from node objects if kubelet is in standalone mode. // Skip reconstructing devicePath from node objects if kubelet is in standalone mode.
// Such kubelet is not expected to mount any attachable volume or Secrets / ConfigMap. // Such kubelet is not expected to mount any attachable volume or Secrets / ConfigMap.
klog.V(2).InfoS("Skipped reconstruction of DevicePaths from node.status in standalone mode") klog.V(2).InfoS("Skipped reconstruction of DevicePaths from node.status in standalone mode")
rc.volumesNeedDevicePath = nil rc.volumesNeedUpdateFromNodeStatus = nil
return return
} }
node, fetchErr := rc.kubeClient.CoreV1().Nodes().Get(context.TODO(), string(rc.nodeName), metav1.GetOptions{}) node, fetchErr := rc.kubeClient.CoreV1().Nodes().Get(context.TODO(), string(rc.nodeName), metav1.GetOptions{})
if fetchErr != nil { if fetchErr != nil {
// This may repeat few times per second until kubelet is able to read its own status for the first time. // This may repeat few times per second until kubelet is able to read its own status for the first time.
klog.V(2).ErrorS(fetchErr, "Failed to get Node status to reconstruct device paths") klog.V(4).ErrorS(fetchErr, "Failed to get Node status to reconstruct device paths")
return return
} }
for _, volumeID := range rc.volumesNeedDevicePath { for _, volumeID := range rc.volumesNeedUpdateFromNodeStatus {
attachable := false
for _, attachedVolume := range node.Status.VolumesAttached { for _, attachedVolume := range node.Status.VolumesAttached {
if volumeID != attachedVolume.Name { if volumeID != attachedVolume.Name {
continue continue
} }
rc.actualStateOfWorld.UpdateReconstructedDevicePath(volumeID, attachedVolume.DevicePath) rc.actualStateOfWorld.UpdateReconstructedDevicePath(volumeID, attachedVolume.DevicePath)
attachable = true
klog.V(4).InfoS("Updated devicePath from node status for volume", "volumeName", attachedVolume.Name, "path", attachedVolume.DevicePath) klog.V(4).InfoS("Updated devicePath from node status for volume", "volumeName", attachedVolume.Name, "path", attachedVolume.DevicePath)
} }
rc.actualStateOfWorld.UpdateReconstructedVolumeAttachability(volumeID, attachable)
} }
klog.V(2).InfoS("DevicePaths of reconstructed volumes updated") klog.V(2).InfoS("DevicePaths of reconstructed volumes updated")
rc.volumesNeedDevicePath = nil rc.volumesNeedUpdateFromNodeStatus = nil
} }

View File

@ -117,8 +117,8 @@ func TestReconstructVolumes(t *testing.T) {
for i := range tc.expectedVolumesNeedDevicePath { for i := range tc.expectedVolumesNeedDevicePath {
expectedVolumes[i] = v1.UniqueVolumeName(tc.expectedVolumesNeedDevicePath[i]) expectedVolumes[i] = v1.UniqueVolumeName(tc.expectedVolumesNeedDevicePath[i])
} }
if !reflect.DeepEqual(expectedVolumes, rcInstance.volumesNeedDevicePath) { if !reflect.DeepEqual(expectedVolumes, rcInstance.volumesNeedUpdateFromNodeStatus) {
t.Errorf("Expected expectedVolumesNeedDevicePath:\n%v\n got:\n%v", expectedVolumes, rcInstance.volumesNeedDevicePath) t.Errorf("Expected expectedVolumesNeedDevicePath:\n%v\n got:\n%v", expectedVolumes, rcInstance.volumesNeedUpdateFromNodeStatus)
} }
expectedVolumes = make([]v1.UniqueVolumeName, len(tc.expectedVolumesNeedReportedInUse)) expectedVolumes = make([]v1.UniqueVolumeName, len(tc.expectedVolumesNeedReportedInUse))
@ -333,7 +333,7 @@ func TestReconstructVolumesMount(t *testing.T) {
return true return true
} }
// Mark devices paths as reconciled to allow unmounting of volumes. // Mark devices paths as reconciled to allow unmounting of volumes.
rcInstance.volumesNeedDevicePath = nil rcInstance.volumesNeedUpdateFromNodeStatus = nil
// Act 2 - reconcile once // Act 2 - reconcile once
rcInstance.reconcileNew() rcInstance.reconcileNew()