Commit 286d8ae2 authored by andyzhangx's avatar andyzhangx

add windows implementation of GetMountRefs

add unit test and fix according to comments fix unit test issue add const in getAllParentLinks func
parent be78d113
...@@ -19,12 +19,7 @@ limitations under the License. ...@@ -19,12 +19,7 @@ limitations under the License.
package mount package mount
import ( import (
"fmt"
"path"
"path/filepath" "path/filepath"
"strings"
"github.com/golang/glog"
) )
const ( const (
...@@ -119,41 +114,6 @@ func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string, ...@@ -119,41 +114,6 @@ func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string,
return mounter.formatAndMount(source, target, fstype, options) 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 // GetMountRefsByDev finds all references to the device provided
// by mountPath; returns a list of paths. // by mountPath; returns a list of paths.
func GetMountRefsByDev(mounter Interface, mountPath string) ([]string, error) { func GetMountRefsByDev(mounter Interface, mountPath string) ([]string, error) {
...@@ -220,34 +180,6 @@ func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, e ...@@ -220,34 +180,6 @@ func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, e
return device, refCount, nil 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. // IsNotMountPoint determines if a directory is a mountpoint.
// It should return ErrNotExist when the directory does not exist. // It should return ErrNotExist when the directory does not exist.
// This method uses the List() of all mountpoints // This method uses the List() of all mountpoints
......
...@@ -22,6 +22,8 @@ import ( ...@@ -22,6 +22,8 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
...@@ -144,6 +146,41 @@ func (m *Mounter) doMount(mounterPath string, mountCmd string, source string, ta ...@@ -144,6 +146,41 @@ func (m *Mounter) doMount(mounterPath string, mountCmd string, source string, ta
return err 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 // detectSystemd returns true if OS runs with systemd as init. When not sure
// (permission errors, ...), it returns false. // (permission errors, ...), it returns false.
// There may be different ways how to detect systemd, this one makes sure that // There may be different ways how to detect systemd, this one makes sure that
...@@ -307,6 +344,34 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str ...@@ -307,6 +344,34 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str
return getDeviceNameFromMount(mounter, mountPath, pluginDir) 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) { func listProcMounts(mountFilePath string) ([]MountPoint, error) {
content, err := utilio.ConsistentRead(mountFilePath, maxListTries) content, err := utilio.ConsistentRead(mountFilePath, maxListTries)
if err != nil { if err != nil {
......
...@@ -39,6 +39,12 @@ func (mounter *Mounter) Unmount(target string) error { ...@@ -39,6 +39,12 @@ func (mounter *Mounter) Unmount(target string) error {
return nil 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) { func (mounter *Mounter) List() ([]MountPoint, error) {
return []MountPoint{}, nil return []MountPoint{}, nil
} }
...@@ -59,6 +65,10 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str ...@@ -59,6 +65,10 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str
return "", nil return "", nil
} }
func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) {
return "", nil
}
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
return false, nil return false, nil
} }
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
...@@ -97,6 +98,16 @@ func (mounter *Mounter) Unmount(target string) error { ...@@ -97,6 +98,16 @@ func (mounter *Mounter) Unmount(target string) error {
return nil 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 // List returns a list of all mounted filesystems. todo
func (mounter *Mounter) List() ([]MountPoint, error) { func (mounter *Mounter) List() ([]MountPoint, error) {
return []MountPoint{}, nil return []MountPoint{}, nil
...@@ -131,6 +142,33 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str ...@@ -131,6 +142,33 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str
return getDeviceNameFromMount(mounter, mountPath, pluginDir) 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 // DeviceOpened determines if the device is in use elsewhere
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
return false, nil return false, nil
...@@ -204,3 +242,30 @@ func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) { ...@@ -204,3 +242,30 @@ func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) {
} }
return string(output)[:1], nil 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
}
...@@ -19,6 +19,8 @@ limitations under the License. ...@@ -19,6 +19,8 @@ limitations under the License.
package mount package mount
import ( import (
"fmt"
"os/exec"
"testing" "testing"
) )
...@@ -69,3 +71,70 @@ func TestValidateDiskNumber(t *testing.T) { ...@@ -69,3 +71,70 @@ func TestValidateDiskNumber(t *testing.T) {
t.Errorf("TestValidateDiskNumber test failed, disk number : %s", diskNum) 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)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment