From 4eca6844fd4f632a79856e117ece01ef7e4e327a Mon Sep 17 00:00:00 2001 From: Shahzeb Patel Date: Tue, 9 May 2017 15:09:07 -0700 Subject: [PATCH] Avoiding redundant mounts by using /proc/mount info (#1224) * Avoiding redundant mounts by using /proc/mount info 1. Convert a short volume name to the long one. If required to a trip to vmdkops service 2. In every mount request, check if the volume is already mounted (info of /proc/mount) 3. refcount test which kills docker and verifies refcounting --- misc/scripts/refcnt_test.sh | 25 +-- vmdk_plugin/Makefile | 2 +- vmdk_plugin/drivers/driver.go | 1 + vmdk_plugin/drivers/photon/photon_driver.go | 65 ++++++-- vmdk_plugin/drivers/vmdk/vmdk_driver.go | 55 ++++++- .../utils/plugin_utils/plugin_utils.go | 148 ++++++++++++++++++ vmdk_plugin/utils/refcount/refcnt.go | 77 +++++---- 7 files changed, 306 insertions(+), 67 deletions(-) create mode 100644 vmdk_plugin/utils/plugin_utils/plugin_utils.go diff --git a/misc/scripts/refcnt_test.sh b/misc/scripts/refcnt_test.sh index 84834f9cf..2c399cb02 100755 --- a/misc/scripts/refcnt_test.sh +++ b/misc/scripts/refcnt_test.sh @@ -67,10 +67,11 @@ function check_files { } function check_recovery_record { - line=`tail -10 /var/log/docker-volume-vsphere.log | $GREP 'Volume name=' | $GREP 'mounted=true'` - expected="name=$vname count=$count mounted=true" + # log contains refcounting attempts and after success logs summary. + line=`tail -50 /var/log/docker-volume-vsphere.log | $GREP 'Volume name=' | $GREP 'mounted=true'` + expected="count=$count mounted=true" - echo $line | $GREP -q "$expected" ; if [ $? -ne 0 ] ; then + echo $line | $GREP "$vname" | $GREP -q "$expected" ; if [ $? -ne 0 ] ; then echo Found: \"$line\" echo Expected pattern: \"$expected\" return 1 @@ -80,20 +81,21 @@ function check_recovery_record { function test_crash_recovery { timeout=$1 - echo "Checking recovery for VMDK plugin kill -9" - kill -9 `pidof docker-volume-vsphere` - until pids=$(pidof docker-volume-vsphere) + echo "Checking recovery through docker kill" + # kill docker daemon forcefully + pkill -9 dockerd + until pids=$(pidof dockerd) do - echo "Waiting for docker-volume-vsphere to restart" + echo "Waiting for docker to restart" sleep 1 done echo "Waiting for plugin init" - sleep 3 + sleep 5 sync # give log the time to flush wait_for check_recovery_record $timeout if [ "$?" -ne 0 ] ; then - echo PLUGIN RESTART TEST FAILED. Did not find proper recovery record + echo DOCKER RESTART TEST FAILED. Did not find proper recovery record exit 1 fi } @@ -115,8 +117,9 @@ fi echo "$(docker volume ls)" for i in `seq 1 $count` do - $DOCKER run -d -v $vname:/v busybox sh -c "touch /v/file$i; sync ; \ - sleep $timeout" + # run containers with restart flag so they restart after docker restart + $DOCKER run -d --restart=always -v $vname:/v busybox sh -c "touch /v/file$i; sync ; \ + while true; do sleep $timeout; done" done echo "Checking the last refcount and mount record" diff --git a/vmdk_plugin/Makefile b/vmdk_plugin/Makefile index fa9ec89b9..b547ab9c0 100644 --- a/vmdk_plugin/Makefile +++ b/vmdk_plugin/Makefile @@ -93,7 +93,7 @@ VMDKOPS_MODULE_SRC = $(VMDKOPS_MODULE)/*.go $(VMCI_SRC) # All sources. We rebuild if anything changes here SRC = main.go log_formatter.go utils/refcount/refcnt.go \ - utils/fs/fs.go utils/config/config.go utils/TestInputParamsUtil.go \ + utils/fs/fs.go utils/config/config.go utils/TestInputParamsUtil.go utils/plugin_utils/plugin_utils.go\ drivers/photon/photon_driver.go drivers/vmdk/vmdk_driver.go # Canned recipe diff --git a/vmdk_plugin/drivers/driver.go b/vmdk_plugin/drivers/driver.go index f177a0486..bce1ab89b 100644 --- a/vmdk_plugin/drivers/driver.go +++ b/vmdk_plugin/drivers/driver.go @@ -20,4 +20,5 @@ type VolumeDriver interface { MountVolume(string, string, string, bool, bool) (string, error) UnmountVolume(string) error GetVolume(string) (map[string]interface{}, error) + VolumesInRefMap() []string } diff --git a/vmdk_plugin/drivers/photon/photon_driver.go b/vmdk_plugin/drivers/photon/photon_driver.go index 8404a5fe2..ef2852612 100644 --- a/vmdk_plugin/drivers/photon/photon_driver.go +++ b/vmdk_plugin/drivers/photon/photon_driver.go @@ -38,6 +38,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/go-plugins-helpers/volume" "github.com/vmware/docker-volume-vsphere/vmdk_plugin/utils/fs" + "github.com/vmware/docker-volume-vsphere/vmdk_plugin/utils/plugin_utils" "github.com/vmware/docker-volume-vsphere/vmdk_plugin/utils/refcount" "github.com/vmware/photon-controller-go-sdk/photon" ) @@ -56,12 +57,13 @@ const ( // VolumeDriver - Photon volume driver struct type VolumeDriver struct { - client *photon.Client - hostID string - mountRoot string - project string - refCounts *refcount.RefCountsMap - target string + client *photon.Client + hostID string + mountRoot string + project string + refCounts *refcount.RefCountsMap + target string + mountIDtoName map[string]string // map of mountID -> full volume name } func (d *VolumeDriver) verifyTarget() error { @@ -95,6 +97,7 @@ func NewVolumeDriver(targetURL string, projectID string, hostID string, mountDir d.mountRoot = mountDir d.refCounts = refcount.NewRefCountsMap() d.refCounts.Init(d, mountDir, driverName) + d.mountIDtoName = make(map[string]string) log.WithFields(log.Fields{ "version": version, @@ -360,8 +363,22 @@ func (d *VolumeDriver) MountVolume(name string, fstype string, id string, isRead return mountpoint, fs.MountWithID(mountpoint, fstype, id, isReadOnly) } +// VolumesInRefMap - get list of volumes names from refmap +// names are in format volume@datastore +func (d *VolumeDriver) VolumesInRefMap() []string { + return d.refCounts.GetVolumeNames() +} + // private function that does the job of mounting volume in conjunction with refcounting func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { + volumeInfo, err := plugin_utils.GetVolumeInfo(r.Name, "", d) + if err != nil { + log.Errorf("Unable to get volume info for volume %s. err:%v", r.Name, err) + return volume.Response{Err: err.Error()} + } + r.Name = volumeInfo.VolumeName + d.mountIDtoName[r.ID] = r.Name + // If the volume is already mounted , just increase the refcount. // Note: for new keys, GO maps return zero value, so no need for if_exists. refcnt := d.incrRefCount(r.Name) // save map traversal @@ -373,22 +390,28 @@ func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { return volume.Response{Mountpoint: d.getMountPoint(r.Name)} } - // There can be redundant mounts till refcounts are properly initialized - // TODO: #1220 - status, err := d.GetVolume(r.Name) - if err != nil { - d.decrRefCount(r.Name) - return volume.Response{Err: err.Error()} + if plugin_utils.AlreadyMounted(r.Name, d.mountRoot) { + log.WithFields(log.Fields{"name": r.Name}).Info("Already mounted, skipping mount. ") + return volume.Response{Mountpoint: d.getMountPoint(r.Name)} + } + + // get volume metadata if required + volumeMeta := volumeInfo.VolumeMeta + if volumeMeta == nil { + if volumeMeta, err = d.GetVolume(r.Name); err != nil { + d.decrRefCount(r.Name) + return volume.Response{Err: err.Error()} + } } - fstype, exists := status[fsTypeTag] + fstype, exists := volumeMeta[fsTypeTag] if !exists { fstype = fs.FstypeDefault } skipAttach := false // If the volume is already attached to the VM, skip the attach. - if state, stateExists := status["State"]; stateExists { + if state, stateExists := volumeMeta["State"]; stateExists { if strings.Compare(state.(string), "DETACHED") != 0 { skipAttach = true } @@ -398,7 +421,7 @@ func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { } // Mount the volume and for now its always read-write. - mountpoint, err := d.MountVolume(r.Name, fstype.(string), status["ID"].(string), false, skipAttach) + mountpoint, err := d.MountVolume(r.Name, fstype.(string), volumeMeta["ID"].(string), false, skipAttach) if err != nil { log.WithFields( log.Fields{"name": r.Name, "error": err.Error()}, @@ -618,6 +641,18 @@ func (d *VolumeDriver) Unmount(r volume.UnmountRequest) volume.Response { return volume.Response{Err: ""} } + if fullVolName, exist := d.mountIDtoName[r.ID]; exist { + r.Name = fullVolName + delete(d.mountIDtoName, r.ID) //cleanup the map + } else { + volumeInfo, err := plugin_utils.GetVolumeInfo(r.Name, "", d) + if err != nil { + log.Errorf("Unable to get volume info for volume %s. err:%v", r.Name, err) + return volume.Response{Err: err.Error()} + } + r.Name = volumeInfo.VolumeName + } + // if refcount has been succcessful, Normal flow. // if the volume is still used by other containers, just return OK refcnt, err := d.decrRefCount(r.Name) diff --git a/vmdk_plugin/drivers/vmdk/vmdk_driver.go b/vmdk_plugin/drivers/vmdk/vmdk_driver.go index ac06eb214..10e6685c6 100644 --- a/vmdk_plugin/drivers/vmdk/vmdk_driver.go +++ b/vmdk_plugin/drivers/vmdk/vmdk_driver.go @@ -34,6 +34,7 @@ import ( "github.com/docker/go-plugins-helpers/volume" "github.com/vmware/docker-volume-vsphere/vmdk_plugin/drivers/vmdk/vmdkops" "github.com/vmware/docker-volume-vsphere/vmdk_plugin/utils/fs" + "github.com/vmware/docker-volume-vsphere/vmdk_plugin/utils/plugin_utils" "github.com/vmware/docker-volume-vsphere/vmdk_plugin/utils/refcount" ) @@ -46,9 +47,10 @@ const ( // VolumeDriver - VMDK driver struct type VolumeDriver struct { - useMockEsx bool - ops vmdkops.VmdkOps - refCounts *refcount.RefCountsMap + useMockEsx bool + ops vmdkops.VmdkOps + refCounts *refcount.RefCountsMap + mountIDtoName map[string]string // map of mountID -> full volume name } var mountRoot string @@ -78,6 +80,7 @@ func NewVolumeDriver(port int, useMockEsx bool, mountDir string, driverName stri } } + d.mountIDtoName = make(map[string]string) d.refCounts.Init(d, mountDir, driverName) log.WithFields(log.Fields{ @@ -89,6 +92,12 @@ func NewVolumeDriver(port int, useMockEsx bool, mountDir string, driverName stri return d } +// VolumesInRefMap - get list of volumes names from refmap +// names are in format volume@datastore +func (d *VolumeDriver) VolumesInRefMap() []string { + return d.refCounts.GetVolumeNames() +} + // In following three operations on refcount, if refcount // map hasn't been initialized, return 1 to prevent detach and remove. @@ -212,6 +221,14 @@ func (d *VolumeDriver) UnmountVolume(name string) error { // private function that does the job of mounting volume in conjunction with refcounting func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { + volumeInfo, err := plugin_utils.GetVolumeInfo(r.Name, "", d) + if err != nil { + log.Errorf("Unable to get volume info for volume %s. err:%v", r.Name, err) + return volume.Response{Err: err.Error()} + } + r.Name = volumeInfo.VolumeName + d.mountIDtoName[r.ID] = r.Name + // If the volume is already mounted , just increase the refcount. // Note: for new keys, GO maps return zero value, so no need for if_exists. refcnt := d.incrRefCount(r.Name) // save map traversal @@ -223,9 +240,19 @@ func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { return volume.Response{Mountpoint: getMountPoint(r.Name)} } - // There can be redundant mounts till refcounts are properly initialized - // TODO: #1220 - status, err := d.ops.Get(r.Name) + if plugin_utils.AlreadyMounted(r.Name, mountRoot) { + log.WithFields(log.Fields{"name": r.Name}).Info("Already mounted, skipping mount. ") + return volume.Response{Mountpoint: getMountPoint(r.Name)} + } + + // get volume metadata if required + volumeMeta := volumeInfo.VolumeMeta + if volumeMeta == nil { + if volumeMeta, err = d.ops.Get(r.Name); err != nil { + d.decrRefCount(r.Name) + return volume.Response{Err: err.Error()} + } + } fstype := fs.FstypeDefault isReadOnly := false @@ -234,7 +261,7 @@ func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { return volume.Response{Err: err.Error()} } // Check access type. - value, exists := status["access"].(string) + value, exists := volumeMeta["access"].(string) if !exists { msg := fmt.Sprintf("Invalid access type for %s, assuming read-write access.", r.Name) log.WithFields(log.Fields{"name": r.Name, "error": msg}).Error("") @@ -244,7 +271,7 @@ func (d *VolumeDriver) processMount(r volume.MountRequest) volume.Response { } // Check file system type. - value, exists = status["fstype"].(string) + value, exists = volumeMeta["fstype"].(string) if !exists { msg := fmt.Sprintf("Invalid filesystem type for %s, assuming type as %s.", r.Name, fstype) @@ -458,6 +485,18 @@ func (d *VolumeDriver) Unmount(r volume.UnmountRequest) volume.Response { return volume.Response{Err: ""} } + if fullVolName, exist := d.mountIDtoName[r.ID]; exist { + r.Name = fullVolName + delete(d.mountIDtoName, r.ID) //cleanup the map + } else { + volumeInfo, err := plugin_utils.GetVolumeInfo(r.Name, "", d) + if err != nil { + log.Errorf("Unable to get volume info for volume %s. err:%v", r.Name, err) + return volume.Response{Err: err.Error()} + } + r.Name = volumeInfo.VolumeName + } + // if refcount has been succcessful, Normal flow // if the volume is still used by other containers, just return OK refcnt, err := d.decrRefCount(r.Name) diff --git a/vmdk_plugin/utils/plugin_utils/plugin_utils.go b/vmdk_plugin/utils/plugin_utils/plugin_utils.go new file mode 100644 index 000000000..500a50116 --- /dev/null +++ b/vmdk_plugin/utils/plugin_utils/plugin_utils.go @@ -0,0 +1,148 @@ +// Copyright 2016 VMware, Inc. All Rights Reserved. +// +// 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 plugin_utils + +// This file holds utility/helper methods required in plugin module + +import ( + "io/ioutil" + "path/filepath" + "strings" + + log "github.com/Sirupsen/logrus" + "github.com/vmware/docker-volume-vsphere/vmdk_plugin/drivers" +) + +const ( + // consts for finding and parsing linux mount information + linuxMountsFile = "/proc/mounts" + + // index datastore from volume meta + // "datastore" key is defined in vmdkops service + datastoreKey = "datastore" +) + +// VolumeInfo - Volume fullname, datastore and metadata +type VolumeInfo struct { + VolumeName string + DatastoreName string + VolumeMeta map[string]interface{} +} + +// GetMountInfo - return a map of mounted volumes and devices +func GetMountInfo(mountRoot string) (map[string]string, error) { + volumeMountMap := make(map[string]string) //map [volume mount path] -> device + data, err := ioutil.ReadFile(linuxMountsFile) + + if err != nil { + log.Errorf("Can't get info from %s (%v)", linuxMountsFile, err) + return volumeMountMap, err + } + + for _, line := range strings.Split(string(data), "\n") { + field := strings.Fields(line) + if len(field) < 2 { + continue // skip empty line and lines too short to have our mount + } + // fields format: [/dev/sdb /mnt/vmdk/vol1 ext2 rw,relatime 0 0] + if filepath.Dir(field[1]) != mountRoot { + continue + } + volumeMountMap[filepath.Base(field[1])] = field[0] + } + return volumeMountMap, nil +} + +// AlreadyMounted - check if volume is already mounted on the mountRoot +func AlreadyMounted(name string, mountRoot string) bool { + volumeMap, err := GetMountInfo(mountRoot) + + if err != nil { + return false + } + + if _, ok := volumeMap[name]; ok { + return true + } + return false +} + +// JoinVolName - return a full name in format volume@datastore +func JoinVolName(volName string, datastoreName string) string { + return strings.Join([]string{volName, datastoreName}, "@") +} + +// SplitVolName - split a volume name into short name and datastore +func SplitVolName(fullVolName string) []string { + return strings.Split(fullVolName, "@") +} + +// IsFullVolName - Check if volume name is full volume name +func IsFullVolName(volName string) bool { + return strings.ContainsAny(volName, "@") +} + +// GetNameFromRefmap - traverse the name entries in refmap +// and if a single entry with volName match is found(no collision), +// use that name as full volume name +func GetNameFromRefmap(volName string, d drivers.VolumeDriver) string { + volumeNameList := d.VolumesInRefMap() + + count := 0 + fullname := "" + + for _, name := range volumeNameList { + refVolName := SplitVolName(name)[0] + if refVolName != volName { + continue + } + // if there are collisions, return + if count > 0 { + return "" + } + count++ + fullname = name + } + return fullname +} + +// GetVolumeInfo - return VolumeInfo with a qualified volume name. +// Optionally returns datastore and volume metadata if retrieved from ESX. +// If Volume Metadata is nil then caller can use getVolume() +func GetVolumeInfo(name string, datastoreName string, d drivers.VolumeDriver) (*VolumeInfo, error) { + // if fullname already, return + if IsFullVolName(name) { + return &VolumeInfo{name, "", nil}, nil + } + + // if datastore name is provided, append and return + if datastoreName != "" { + return &VolumeInfo{JoinVolName(name, datastoreName), datastoreName, nil}, nil + } + + // find full volume names using refmap if possible + if fullVolumeName := GetNameFromRefmap(name, d); fullVolumeName != "" { + return &VolumeInfo{fullVolumeName, "", nil}, nil + } + + // Do a get trip to esx and construct full name + volumeMeta, err := d.GetVolume(name) + if err != nil { + log.Errorf("Unable to get volume metadata %s (err: %v)", name, err) + return nil, err + } + datastoreName = volumeMeta[datastoreKey].(string) + return &VolumeInfo{JoinVolName(name, datastoreName), datastoreName, volumeMeta}, nil +} diff --git a/vmdk_plugin/utils/refcount/refcnt.go b/vmdk_plugin/utils/refcount/refcnt.go index df816cda3..e9b6a4255 100644 --- a/vmdk_plugin/utils/refcount/refcnt.go +++ b/vmdk_plugin/utils/refcount/refcnt.go @@ -73,8 +73,6 @@ package refcount import ( "fmt" - "io/ioutil" - "path/filepath" "strings" "sync" "time" @@ -84,6 +82,7 @@ import ( "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" "github.com/vmware/docker-volume-vsphere/vmdk_plugin/drivers" + "github.com/vmware/docker-volume-vsphere/vmdk_plugin/utils/plugin_utils" "golang.org/x/net/context" ) @@ -95,9 +94,7 @@ const ( refCountDelayStartSec = 2 refCountRetryAttempts = 20 - // consts for finding and parsing linux mount information - linuxMountsFile = "/proc/mounts" - photonDriver = "photon" + photonDriver = "photon" ) // info about individual volume ref counts and mount @@ -119,9 +116,9 @@ type RefCountsMap struct { refMap map[string]*refCount // Map of refCounts mtx *sync.RWMutex // Synchronizes RefCountsMap ops - refcntInitSuccess bool // save refcounting success - isDirty bool // flag to check reconciling has been interrupted - StateMtx *sync.Mutex // (Exported) Synchronizes refcounting between mount/unmount and refcounting thread + refcntInitSuccess bool // save refcounting success + isDirty bool // flag to check reconciling has been interrupted + StateMtx *sync.Mutex // (Exported) Synchronizes refcounting between mount/unmount and refcounting thread } var ( @@ -267,6 +264,21 @@ func (r *RefCountsMap) GetCount(vol string) uint { return rc.count } +// GetVolumeNames - return fully qualified volume names from refMap +func (r *RefCountsMap) GetVolumeNames() []string { + r.mtx.RLock() + defer r.mtx.RUnlock() + var volumeList []string + + for k := range r.refMap { + volumeList = append(volumeList, k) + } + + // the list contains full volume names in format volume@datastore + // there can be volumes with same name on different datastores + return volumeList +} + // Incr refCount for the volume vol. Creates new entry if needed. func (r *RefCountsMap) Incr(vol string) uint { // Locks the RefCountsMap @@ -316,8 +328,8 @@ func (r *RefCountsMap) Decr(vol string) (uint, error) { return rc.count, nil } -// check if volume with source as mount_source belongs to vsphere plugin -func matchNameforVMDK(mount_source string) bool { +// check if volume with source as mount_source belongs to vmdk plugin +func isVMDKMount(mount_source string) bool { managedPluginMountStart := "/var/lib/docker/plugins/" // if plugin is used as a service @@ -366,6 +378,9 @@ func (r *RefCountsMap) discoverAndSync(c *client.Client, d drivers.VolumeDriver) return err } + // use same datastore for all volumes with short names + datastoreName := "" + log.Infof("Found %d running or paused containers", len(containers)) for _, ct := range containers { @@ -383,14 +398,21 @@ func (r *RefCountsMap) discoverAndSync(c *client.Client, d drivers.VolumeDriver) return err } log.Debugf(" Mounts for %v", ct.Names) - for _, mount := range containerJSONInfo.Mounts { - // check if the mount location belongs to vsphere plugin - if matchNameforVMDK(mount.Source) { - r.Incr(mount.Name) - log.Infof(" name=%v (driver=%s source=%s) (%v)", - mount.Name, mount.Driver, mount.Source, mount) + // check if the mount location belongs to vmdk plugin + if isVMDKMount(mount.Source) != true { + continue + } + + volumeInfo, err := plugin_utils.GetVolumeInfo(mount.Name, datastoreName, d) + if err != nil { + log.Errorf("Unable to get volume info for volume %s. err:%v", mount.Name, err) + return err } + datastoreName = volumeInfo.DatastoreName + r.Incr(volumeInfo.VolumeName) + log.Debugf("name=%v (driver=%s source=%s) (%v)", + mount.Name, mount.Driver, mount.Source, mount) } } @@ -407,7 +429,7 @@ func (r *RefCountsMap) discoverAndSync(c *client.Client, d drivers.VolumeDriver) // Check that refcounts and actual mount info from Linux match // If they don't, unmount unneeded stuff, or yell if something is // not mounted but should be (it's error. we should not get there) - r.getMountInfo() + r.updateRefMap() r.syncMountsWithRefCounters(d) // mark reconciling success so that further unmounts can instantly be processed r.refcntInitSuccess = true @@ -479,33 +501,24 @@ func (r *RefCountsMap) syncMountsWithRefCounters(d drivers.VolumeDriver) { } } -// scans /proc/mounts and updates refcount map witn mounted volumes -func (r *RefCountsMap) getMountInfo() error { +// updates refcount map with mounted volumes using mount info +func (r *RefCountsMap) updateRefMap() error { r.mtx.Lock() defer r.mtx.Unlock() - data, err := ioutil.ReadFile(linuxMountsFile) + volumeMap, err := plugin_utils.GetMountInfo(mountRoot) + if err != nil { - log.Errorf("Can't get info from %s (%v)", linuxMountsFile, err) return err } - for _, line := range strings.Split(string(data), "\n") { - field := strings.Fields(line) - if len(field) < 2 { - continue // skip empty line and lines too short to have our mount - } - // fields format: [/dev/sdb /mnt/vmdk/vol1 ext2 rw,relatime 0 0] - if filepath.Dir(field[1]) != mountRoot { - continue - } - volName := filepath.Base(field[1]) + for volName, dev := range volumeMap { refInfo := r.refMap[volName] if refInfo == nil { refInfo = newRefCount() } refInfo.mounted = true - refInfo.dev = field[0] + refInfo.dev = dev r.refMap[volName] = refInfo log.Debugf("Found '%s' in /proc/mount, ref=(%#v)", volName, refInfo) }