Merge pull request #30661 from saad-ali/safeDeviceUnmount

Automatic merge from submit-queue

Prevent device unmount from deleting dir on failed unmount

This PR cleans up the device unmount code for attachable volumes. Specifically it:
* Prevents deletion of directory via `os.Remove` unless unmount succeeds.
* Moves common shared device unmount logic to a common util file.
This commit is contained in:
Kubernetes Submit Queue 2016-08-15 20:02:30 -07:00 committed by GitHub
commit c5ab95cd79
6 changed files with 50 additions and 101 deletions

View File

@ -28,6 +28,7 @@ import (
"k8s.io/kubernetes/pkg/util/exec" "k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
) )
type awsElasticBlockStoreAttacher struct { type awsElasticBlockStoreAttacher struct {
@ -218,7 +219,7 @@ func (detacher *awsElasticBlockStoreDetacher) WaitForDetach(devicePath string, t
select { select {
case <-ticker.C: case <-ticker.C:
glog.V(5).Infof("Checking device %q is detached.", devicePath) glog.V(5).Infof("Checking device %q is detached.", devicePath)
if pathExists, err := pathExists(devicePath); err != nil { if pathExists, err := volumeutil.PathExists(devicePath); err != nil {
return fmt.Errorf("Error checking if device path exists: %v", err) return fmt.Errorf("Error checking if device path exists: %v", err)
} else if !pathExists { } else if !pathExists {
return nil return nil
@ -230,10 +231,5 @@ func (detacher *awsElasticBlockStoreDetacher) WaitForDetach(devicePath string, t
} }
func (detacher *awsElasticBlockStoreDetacher) UnmountDevice(deviceMountPath string) error { func (detacher *awsElasticBlockStoreDetacher) UnmountDevice(deviceMountPath string) error {
volume := path.Base(deviceMountPath) return volumeutil.UnmountPath(deviceMountPath, detacher.mounter)
if err := unmountPDAndRemoveGlobalPath(deviceMountPath, detacher.mounter); err != nil {
glog.Errorf("Error unmounting %q: %v", volume, err)
}
return nil
} }

View File

@ -18,14 +18,13 @@ package aws_ebs
import ( import (
"fmt" "fmt"
"os"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws" "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
) )
const ( const (
@ -104,7 +103,7 @@ func (util *AWSDiskUtil) CreateVolume(c *awsElasticBlockStoreProvisioner) (strin
// Returns the first path that exists, or empty string if none exist. // Returns the first path that exists, or empty string if none exist.
func verifyDevicePath(devicePaths []string) (string, error) { func verifyDevicePath(devicePaths []string) (string, error) {
for _, path := range devicePaths { for _, path := range devicePaths {
if pathExists, err := pathExists(path); err != nil { if pathExists, err := volumeutil.PathExists(path); err != nil {
return "", fmt.Errorf("Error checking if path exists: %v", err) return "", fmt.Errorf("Error checking if path exists: %v", err)
} else if pathExists { } else if pathExists {
return path, nil return path, nil
@ -114,24 +113,11 @@ func verifyDevicePath(devicePaths []string) (string, error) {
return "", nil return "", nil
} }
// Unmount the global mount path, which should be the only one, and delete it.
func unmountPDAndRemoveGlobalPath(globalMountPath string, mounter mount.Interface) error {
if pathExists, pathErr := pathExists(globalMountPath); pathErr != nil {
return fmt.Errorf("Error checking if path exists: %v", pathErr)
} else if !pathExists {
glog.V(5).Infof("Warning: Unmount skipped because path does not exist: %v", globalMountPath)
return nil
}
err := mounter.Unmount(globalMountPath)
os.Remove(globalMountPath)
return err
}
// Returns the first path that exists, or empty string if none exist. // Returns the first path that exists, or empty string if none exist.
func verifyAllPathsRemoved(devicePaths []string) (bool, error) { func verifyAllPathsRemoved(devicePaths []string) (bool, error) {
allPathsRemoved := true allPathsRemoved := true
for _, path := range devicePaths { for _, path := range devicePaths {
if exists, err := pathExists(path); err != nil { if exists, err := volumeutil.PathExists(path); err != nil {
return false, fmt.Errorf("Error checking if path exists: %v", err) return false, fmt.Errorf("Error checking if path exists: %v", err)
} else { } else {
allPathsRemoved = allPathsRemoved && !exists allPathsRemoved = allPathsRemoved && !exists
@ -159,18 +145,6 @@ func getDiskByIdPaths(partition string, devicePath string) []string {
return devicePaths return devicePaths
} }
// Checks if the specified path exists
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
} else if os.IsNotExist(err) {
return false, nil
} else {
return false, err
}
}
// Return cloud provider // Return cloud provider
func getCloudProvider(cloudProvider cloudprovider.Interface) (*aws.Cloud, error) { func getCloudProvider(cloudProvider cloudprovider.Interface) (*aws.Cloud, error) {
awsCloudProvider, ok := cloudProvider.(*aws.Cloud) awsCloudProvider, ok := cloudProvider.(*aws.Cloud)

View File

@ -27,6 +27,7 @@ import (
"k8s.io/kubernetes/pkg/util/exec" "k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
) )
type cinderDiskAttacher struct { type cinderDiskAttacher struct {
@ -130,7 +131,7 @@ func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath
case <-ticker.C: case <-ticker.C:
glog.V(5).Infof("Checking Cinder disk %q is attached.", volumeID) glog.V(5).Infof("Checking Cinder disk %q is attached.", volumeID)
probeAttachedVolume() probeAttachedVolume()
exists, err := pathExists(devicePath) exists, err := volumeutil.PathExists(devicePath)
if exists && err == nil { if exists && err == nil {
glog.Infof("Successfully found attached Cinder disk %q.", volumeID) glog.Infof("Successfully found attached Cinder disk %q.", volumeID)
return devicePath, nil return devicePath, nil
@ -250,7 +251,7 @@ func (detacher *cinderDiskDetacher) WaitForDetach(devicePath string, timeout tim
select { select {
case <-ticker.C: case <-ticker.C:
glog.V(5).Infof("Checking device %q is detached.", devicePath) glog.V(5).Infof("Checking device %q is detached.", devicePath)
if pathExists, err := pathExists(devicePath); err != nil { if pathExists, err := volumeutil.PathExists(devicePath); err != nil {
return fmt.Errorf("Error checking if device path exists: %v", err) return fmt.Errorf("Error checking if device path exists: %v", err)
} else if !pathExists { } else if !pathExists {
return nil return nil
@ -262,35 +263,5 @@ func (detacher *cinderDiskDetacher) WaitForDetach(devicePath string, timeout tim
} }
func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string) error { func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string) error {
volume := path.Base(deviceMountPath) return volumeutil.UnmountPath(deviceMountPath, detacher.mounter)
if err := unmountPDAndRemoveGlobalPath(deviceMountPath, detacher.mounter); err != nil {
glog.Errorf("Error unmounting %q: %v", volume, err)
}
return nil
}
// Checks if the specified path exists
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
} else if os.IsNotExist(err) {
return false, nil
} else {
return false, err
}
}
// Unmount the global mount path, which should be the only one, and delete it.
func unmountPDAndRemoveGlobalPath(globalMountPath string, mounter mount.Interface) error {
if pathExists, pathErr := pathExists(globalMountPath); pathErr != nil {
return fmt.Errorf("Error checking if path exists: %v", pathErr)
} else if !pathExists {
glog.V(5).Infof("Warning: Unmount skipped because path does not exist: %v", globalMountPath)
return nil
}
err := mounter.Unmount(globalMountPath)
os.Remove(globalMountPath)
return err
} }

View File

@ -30,6 +30,7 @@ import (
"k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
) )
type gcePersistentDiskAttacher struct { type gcePersistentDiskAttacher struct {
@ -244,7 +245,7 @@ func (detacher *gcePersistentDiskDetacher) WaitForDetach(devicePath string, time
select { select {
case <-ticker.C: case <-ticker.C:
glog.V(5).Infof("Checking device %q is detached.", devicePath) glog.V(5).Infof("Checking device %q is detached.", devicePath)
if pathExists, err := pathExists(devicePath); err != nil { if pathExists, err := volumeutil.PathExists(devicePath); err != nil {
return fmt.Errorf("Error checking if device path exists: %v", err) return fmt.Errorf("Error checking if device path exists: %v", err)
} else if !pathExists { } else if !pathExists {
return nil return nil
@ -256,5 +257,5 @@ func (detacher *gcePersistentDiskDetacher) WaitForDetach(devicePath string, time
} }
func (detacher *gcePersistentDiskDetacher) UnmountDevice(deviceMountPath string) error { func (detacher *gcePersistentDiskDetacher) UnmountDevice(deviceMountPath string) error {
return unmountPDAndRemoveGlobalPath(deviceMountPath, detacher.host.GetMounter()) return volumeutil.UnmountPath(deviceMountPath, detacher.host.GetMounter())
} }

View File

@ -18,7 +18,6 @@ package gce_pd
import ( import (
"fmt" "fmt"
"os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -28,9 +27,9 @@ import (
"k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider"
gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
"k8s.io/kubernetes/pkg/util/exec" "k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
) )
const ( const (
@ -114,7 +113,7 @@ func verifyDevicePath(devicePaths []string, sdBeforeSet sets.String) (string, er
} }
for _, path := range devicePaths { for _, path := range devicePaths {
if pathExists, err := pathExists(path); err != nil { if pathExists, err := volumeutil.PathExists(path); err != nil {
return "", fmt.Errorf("Error checking if path exists: %v", err) return "", fmt.Errorf("Error checking if path exists: %v", err)
} else if pathExists { } else if pathExists {
return path, nil return path, nil
@ -124,20 +123,6 @@ func verifyDevicePath(devicePaths []string, sdBeforeSet sets.String) (string, er
return "", nil return "", nil
} }
// Unmount the global PD mount, which should be the only one, and delete it.
// Does nothing if globalMountPath does not exist.
func unmountPDAndRemoveGlobalPath(globalMountPath string, mounter mount.Interface) error {
if pathExists, pathErr := pathExists(globalMountPath); pathErr != nil {
return fmt.Errorf("Error checking if path exists: %v", pathErr)
} else if !pathExists {
glog.V(5).Infof("Warning: Unmount skipped because path does not exist: %v", globalMountPath)
return nil
}
err := mounter.Unmount(globalMountPath)
os.Remove(globalMountPath)
return err
}
// Returns the first path that exists, or empty string if none exist. // Returns the first path that exists, or empty string if none exist.
func verifyAllPathsRemoved(devicePaths []string) (bool, error) { func verifyAllPathsRemoved(devicePaths []string) (bool, error) {
allPathsRemoved := true allPathsRemoved := true
@ -146,7 +131,7 @@ func verifyAllPathsRemoved(devicePaths []string) (bool, error) {
// udevadm errors should not block disk detachment, log and continue // udevadm errors should not block disk detachment, log and continue
glog.Errorf("%v", err) glog.Errorf("%v", err)
} }
if exists, err := pathExists(path); err != nil { if exists, err := volumeutil.PathExists(path); err != nil {
return false, fmt.Errorf("Error checking if path exists: %v", err) return false, fmt.Errorf("Error checking if path exists: %v", err)
} else { } else {
allPathsRemoved = allPathsRemoved && !exists allPathsRemoved = allPathsRemoved && !exists
@ -172,18 +157,6 @@ func getDiskByIdPaths(pdName string, partition string) []string {
return devicePaths return devicePaths
} }
// Checks if the specified path exists
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
} else if os.IsNotExist(err) {
return false, nil
} else {
return false, err
}
}
// Return cloud provider // Return cloud provider
func getCloudProvider(cloudProvider cloudprovider.Interface) (*gcecloud.GCECloud, error) { func getCloudProvider(cloudProvider cloudprovider.Interface) (*gcecloud.GCECloud, error) {
var err error var err error

View File

@ -17,10 +17,12 @@ limitations under the License.
package util package util
import ( import (
"fmt"
"os" "os"
"path" "path"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/util/mount"
) )
const readyFileName = "ready" const readyFileName = "ready"
@ -60,3 +62,35 @@ func SetReady(dir string) {
} }
file.Close() file.Close()
} }
// UnmountPath is a common unmount routine that unmounts the given path and
// deletes the remaining directory if successful.
func UnmountPath(mountPath string, mounter mount.Interface) error {
if pathExists, pathErr := PathExists(mountPath); pathErr != nil {
return fmt.Errorf("Error checking if path exists: %v", pathErr)
} else if !pathExists {
glog.Warningf("Warning: Unmount skipped because path does not exist: %v", mountPath)
return nil
}
err := mounter.Unmount(mountPath)
if err == nil {
// Only delete directory on successful unmount
glog.V(5).Infof("Unmounted %q. Deleting path.", mountPath)
return os.Remove(mountPath)
}
return err
}
// PathExists returns true if the specified path exists.
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
} else if os.IsNotExist(err) {
return false, nil
} else {
return false, err
}
}