Merge pull request #52401 from andyzhangx/azuredisk-getdevfunc

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

fix warning messages due to GetMountRefs func not implemented in windows

**What this PR does / why we need it**:
This PR completes the windows implementation of GetMountRefs in mount.go. In linux, the GetMountRefs implementaion is: read `/proc/mounts` and find all mount points, while in Windows, there is no such `/proc/mounts` place which shows all mounting points. 
There is another way in windows, **we could walk through(by `getAllParentLinks` func) the mount path(symbolic link) and get all symlinks until we got the final device, which is actually a drive**.

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #54670
This PR fixed the warnning issue mentioned in https://github.com/kubernetes/kubernetes/pull/51252

**Special notes for your reviewer**:
Some values in the code would be like follwoing:
```
GetMountRefs: mountPath ("\\var\\lib\\kubelet\\pods/4c74b128-92ca-11e7-b86b-000d3a36d70c/volumes/kubernetes.io~azure-disk/pvc-1cc91c70-92ca-11e7-b86b-000d3a36d70c")
getAllParentLinks: refs (["" "" "c:\\var\\lib\\kubelet\\plugins\\kubernetes.io\\azure-disk\\mounts\\b1246717734" "G:\\"])
basemountPath c:\var\lib\kubelet\plugins\kubernetes.io\azure-disk\mounts
got volumeID b1246717734
```

**Release note**:

```
fix warning messages due to GetMountRefs func not implemented in windows
```
pull/6/head
Kubernetes Submit Queue 2017-10-27 20:28:36 -07:00 committed by GitHub
commit f34a24e98e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 209 additions and 68 deletions

View File

@ -19,12 +19,7 @@ limitations under the License.
package mount
import (
"fmt"
"path"
"path/filepath"
"strings"
"github.com/golang/glog"
)
type FileType string
@ -138,41 +133,6 @@ func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string,
return mounter.formatAndMount(source, target, fstype, options)
}
// GetMountRefs finds all other references to the device referenced
// by mountPath; returns a list of paths.
func GetMountRefs(mounter Interface, mountPath string) ([]string, error) {
mps, err := mounter.List()
if err != nil {
return nil, err
}
// Find the device name.
deviceName := ""
// If mountPath is symlink, need get its target path.
slTarget, err := filepath.EvalSymlinks(mountPath)
if err != nil {
slTarget = mountPath
}
for i := range mps {
if mps[i].Path == slTarget {
deviceName = mps[i].Device
break
}
}
// Find all references to the device.
var refs []string
if deviceName == "" {
glog.Warningf("could not determine device for path: %q", mountPath)
} else {
for i := range mps {
if mps[i].Device == deviceName && mps[i].Path != slTarget {
refs = append(refs, mps[i].Path)
}
}
}
return refs, nil
}
// GetMountRefsByDev finds all references to the device provided
// by mountPath; returns a list of paths.
func GetMountRefsByDev(mounter Interface, mountPath string) ([]string, error) {
@ -239,34 +199,6 @@ func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, e
return device, refCount, nil
}
// getDeviceNameFromMount find the device name from /proc/mounts in which
// the mount path reference should match the given plugin directory. In case no mount path reference
// matches, returns the volume name taken from its given mountPath
func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) {
refs, err := GetMountRefs(mounter, mountPath)
if err != nil {
glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err)
return "", err
}
if len(refs) == 0 {
glog.V(4).Infof("Directory %s is not mounted", mountPath)
return "", fmt.Errorf("directory %s is not mounted", mountPath)
}
basemountPath := path.Join(pluginDir, MountsInGlobalPDPath)
for _, ref := range refs {
if strings.HasPrefix(ref, basemountPath) {
volumeID, err := filepath.Rel(basemountPath, ref)
if err != nil {
glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err)
return "", err
}
return volumeID, nil
}
}
return path.Base(mountPath), nil
}
// IsNotMountPoint determines if a directory is a mountpoint.
// It should return ErrNotExist when the directory does not exist.
// This method uses the List() of all mountpoints

View File

@ -22,6 +22,8 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
@ -141,6 +143,41 @@ func (m *Mounter) doMount(mounterPath string, mountCmd string, source string, ta
return err
}
// GetMountRefs finds all other references to the device referenced
// by mountPath; returns a list of paths.
func GetMountRefs(mounter Interface, mountPath string) ([]string, error) {
mps, err := mounter.List()
if err != nil {
return nil, err
}
// Find the device name.
deviceName := ""
// If mountPath is symlink, need get its target path.
slTarget, err := filepath.EvalSymlinks(mountPath)
if err != nil {
slTarget = mountPath
}
for i := range mps {
if mps[i].Path == slTarget {
deviceName = mps[i].Device
break
}
}
// Find all references to the device.
var refs []string
if deviceName == "" {
glog.Warningf("could not determine device for path: %q", mountPath)
} else {
for i := range mps {
if mps[i].Device == deviceName && mps[i].Path != slTarget {
refs = append(refs, mps[i].Path)
}
}
}
return refs, nil
}
// detectSystemd returns true if OS runs with systemd as init. When not sure
// (permission errors, ...), it returns false.
// There may be different ways how to detect systemd, this one makes sure that
@ -299,6 +336,34 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str
return getDeviceNameFromMount(mounter, mountPath, pluginDir)
}
// getDeviceNameFromMount find the device name from /proc/mounts in which
// the mount path reference should match the given plugin directory. In case no mount path reference
// matches, returns the volume name taken from its given mountPath
func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) {
refs, err := GetMountRefs(mounter, mountPath)
if err != nil {
glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err)
return "", err
}
if len(refs) == 0 {
glog.V(4).Infof("Directory %s is not mounted", mountPath)
return "", fmt.Errorf("directory %s is not mounted", mountPath)
}
basemountPath := path.Join(pluginDir, MountsInGlobalPDPath)
for _, ref := range refs {
if strings.HasPrefix(ref, basemountPath) {
volumeID, err := filepath.Rel(basemountPath, ref)
if err != nil {
glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err)
return "", err
}
return volumeID, nil
}
}
return path.Base(mountPath), nil
}
func listProcMounts(mountFilePath string) ([]MountPoint, error) {
content, err := utilio.ConsistentRead(mountFilePath, maxListTries)
if err != nil {

View File

@ -43,6 +43,12 @@ func (mounter *Mounter) Unmount(target string) error {
return nil
}
// GetMountRefs finds all other references to the device referenced
// by mountPath; returns a list of paths.
func GetMountRefs(mounter Interface, mountPath string) ([]string, error) {
return []string{}, nil
}
func (mounter *Mounter) List() ([]MountPoint, error) {
return []MountPoint{}, nil
}
@ -63,6 +69,10 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str
return "", nil
}
func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) {
return "", nil
}
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
return false, nil
}

View File

@ -22,6 +22,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
@ -118,6 +119,16 @@ func (mounter *Mounter) Unmount(target string) error {
return nil
}
// GetMountRefs finds all other references to the device(drive) referenced
// by mountPath; returns a list of paths.
func GetMountRefs(mounter Interface, mountPath string) ([]string, error) {
refs, err := getAllParentLinks(normalizeWindowsPath(mountPath))
if err != nil {
return nil, err
}
return refs, nil
}
// List returns a list of all mounted filesystems. todo
func (mounter *Mounter) List() ([]MountPoint, error) {
return []MountPoint{}, nil
@ -152,6 +163,33 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str
return getDeviceNameFromMount(mounter, mountPath, pluginDir)
}
// getDeviceNameFromMount find the device(drive) name in which
// the mount path reference should match the given plugin directory. In case no mount path reference
// matches, returns the volume name taken from its given mountPath
func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) {
refs, err := GetMountRefs(mounter, mountPath)
if err != nil {
glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err)
return "", err
}
if len(refs) == 0 {
return "", fmt.Errorf("directory %s is not mounted", mountPath)
}
basemountPath := normalizeWindowsPath(path.Join(pluginDir, MountsInGlobalPDPath))
for _, ref := range refs {
if strings.Contains(ref, basemountPath) {
volumeID, err := filepath.Rel(normalizeWindowsPath(basemountPath), ref)
if err != nil {
glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err)
return "", err
}
return volumeID, nil
}
}
return path.Base(mountPath), nil
}
// DeviceOpened determines if the device is in use elsewhere
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
return false, nil
@ -300,3 +338,30 @@ func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) {
}
return string(output)[:1], nil
}
// getAllParentLinks walks all symbolic links and return all the parent targets recursively
func getAllParentLinks(path string) ([]string, error) {
const maxIter = 255
links := []string{}
for {
links = append(links, path)
if len(links) > maxIter {
return links, fmt.Errorf("unexpected length of parent links: %v", links)
}
fi, err := os.Lstat(path)
if err != nil {
return links, fmt.Errorf("Lstat: %v", err)
}
if fi.Mode()&os.ModeSymlink == 0 {
break
}
path, err = os.Readlink(path)
if err != nil {
return links, fmt.Errorf("Readlink error: %v", err)
}
}
return links, nil
}

View File

@ -19,6 +19,8 @@ limitations under the License.
package mount
import (
"fmt"
"os/exec"
"testing"
)
@ -69,3 +71,70 @@ func TestValidateDiskNumber(t *testing.T) {
t.Errorf("TestValidateDiskNumber test failed, disk number : %s", diskNum)
}
}
func makeLink(link, target string) error {
if output, err := exec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput(); err != nil {
return fmt.Errorf("mklink failed: %v, link(%q) target(%q) output: %q", err, link, target, string(output))
}
return nil
}
func removeLink(link string) error {
if output, err := exec.Command("cmd", "/c", "rmdir", link).CombinedOutput(); err != nil {
return fmt.Errorf("rmdir failed: %v, output: %q", err, string(output))
}
return nil
}
func setEquivalent(set1, set2 []string) bool {
map1 := make(map[string]bool)
map2 := make(map[string]bool)
for _, s := range set1 {
map1[s] = true
}
for _, s := range set2 {
map2[s] = true
}
for s := range map1 {
if !map2[s] {
return false
}
}
for s := range map2 {
if !map1[s] {
return false
}
}
return true
}
// this func must run in admin mode, otherwise it will fail
func TestGetMountRefs(t *testing.T) {
fm := &FakeMounter{MountPoints: []MountPoint{}}
mountPath := `c:\secondmountpath`
expectedRefs := []string{`c:\`, `c:\firstmountpath`, mountPath}
// remove symbolic links first
for i := 1; i < len(expectedRefs); i++ {
removeLink(expectedRefs[i])
}
// create symbolic links
for i := 1; i < len(expectedRefs); i++ {
if err := makeLink(expectedRefs[i], expectedRefs[i-1]); err != nil {
t.Errorf("makeLink failed: %v", err)
}
}
if refs, err := GetMountRefs(fm, mountPath); err != nil || !setEquivalent(expectedRefs, refs) {
t.Errorf("getMountRefs(%q) = %v, error: %v; expected %v", mountPath, refs, err, expectedRefs)
}
// remove symbolic links
for i := 1; i < len(expectedRefs); i++ {
if err := removeLink(expectedRefs[i]); err != nil {
t.Errorf("removeLink failed: %v", err)
}
}
}