Skip to content

Commit b95de8b

Browse files
committed
Refactor MountManager for testability, really wish golang had extendable interfaces right about now
1 parent 3d48871 commit b95de8b

File tree

7 files changed

+285
-197
lines changed

7 files changed

+285
-197
lines changed

cmd/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
gce "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider"
2424
driver "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-pd-csi-driver"
25+
mountmanager "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/mount-manager"
2526
)
2627

2728
func init() {
@@ -55,7 +56,7 @@ func handle() {
5556
glog.Fatalf("Failed to get cloud provider: %v", err)
5657
}
5758

58-
mounter := driver.NewSafeFormatAndMounter()
59+
mounter := mountmanager.NewMounter()
5960

6061
err = gceDriver.SetupGCEDriver(cloudProvider, mounter, *driverName, *nodeID, vendorVersion)
6162
if err != nil {

deploy/setup-project.sh

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ sed -i "/serviceAccount:${IAM_NAME}/d" "${PKGDIR}/deploy/iam.json"
3131
gcloud projects set-iam-policy "${PROJECT}" "${PKGDIR}/deploy/iam.json"
3232
rm -f "${PKGDIR}/deploy/iam.json"
3333
# Delete Service Account
34-
gcloud iam service-accounts delete "$IAM_NAME" --quiet || true
34+
gcloud iam service-accounts delete "${IAM_NAME}" --project "${PROJECT}" --quiet || true
3535

3636
# Create new Service Account and Keys
37-
gcloud iam service-accounts create "${GCEPD_SA_NAME}"
37+
gcloud iam service-accounts create "${GCEPD_SA_NAME}" --project "${PROJECT}"
3838
for role in ${BIND_ROLES}
3939
do
4040
gcloud projects add-iam-policy-binding "${PROJECT}" --member serviceAccount:"${IAM_NAME}" --role ${role}
4141
done
42-
gcloud iam service-accounts keys create "${SA_FILE}" --iam-account "${IAM_NAME}"
42+
gcloud iam service-accounts keys create "${SA_FILE}" --iam-account "${IAM_NAME}" --project "${PROJECT}"

pkg/gce-pd-csi-driver/gce-pd-driver.go

+3-42
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import (
2121
"github.com/golang/glog"
2222
"google.golang.org/grpc/codes"
2323
"google.golang.org/grpc/status"
24-
"k8s.io/kubernetes/pkg/util/mount"
2524
gce "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider"
25+
mountmanager "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/mount-manager"
2626
)
2727

2828
type GCEDriver struct {
@@ -43,7 +43,7 @@ func GetGCEDriver() *GCEDriver {
4343
return &GCEDriver{}
4444
}
4545

46-
func (gceDriver *GCEDriver) SetupGCEDriver(cloudProvider gce.GCECompute, mounter mount.SafeFormatAndMount, name, nodeID, vendorVersion string) error {
46+
func (gceDriver *GCEDriver) SetupGCEDriver(cloudProvider gce.GCECompute, mounter mountmanager.MountManager, name, nodeID, vendorVersion string) error {
4747
if name == "" {
4848
return fmt.Errorf("Driver name missing")
4949
}
@@ -129,7 +129,7 @@ func NewIdentityServer(gceDriver *GCEDriver) *GCEIdentityServer {
129129
}
130130
}
131131

132-
func NewNodeServer(gceDriver *GCEDriver, mounter mount.SafeFormatAndMount) *GCENodeServer {
132+
func NewNodeServer(gceDriver *GCEDriver, mounter mountmanager.MountManager) *GCENodeServer {
133133
return &GCENodeServer{
134134
Driver: gceDriver,
135135
Mounter: mounter,
@@ -143,45 +143,6 @@ func NewControllerServer(gceDriver *GCEDriver, cloudProvider gce.GCECompute) *GC
143143
}
144144
}
145145

146-
func NewSafeFormatAndMounter() mount.SafeFormatAndMount {
147-
realMounter := mount.New("")
148-
realExec := mount.NewOsExec()
149-
return mount.SafeFormatAndMount{
150-
Interface: realMounter,
151-
Exec: realExec,
152-
}
153-
}
154-
155-
func NewFakeSafeFormatAndMounter(mountErrs []error) mount.SafeFormatAndMount {
156-
execCallback := func(cmd string, args ...string) ([]byte, error) {
157-
return nil, nil
158-
// TODO: Fill out exec callback for errors
159-
/*
160-
if len(test.execScripts) <= execCallCount {
161-
t.Errorf("Unexpected command: %s %v", cmd, args)
162-
return nil, nil
163-
}
164-
script := test.execScripts[execCallCount]
165-
execCallCount++
166-
if script.command != cmd {
167-
t.Errorf("Unexpected command %s. Expecting %s", cmd, script.command)
168-
}
169-
for j := range args {
170-
if args[j] != script.args[j] {
171-
t.Errorf("Unexpected args %v. Expecting %v", args, script.args)
172-
}
173-
}
174-
return []byte(script.output), script.err
175-
*/
176-
}
177-
fakeMounter := &mount.FakeMounter{MountPoints: []mount.MountPoint{}, Log: []mount.FakeAction{}}
178-
fakeExec := mount.NewFakeExec(execCallback)
179-
return mount.SafeFormatAndMount{
180-
Interface: fakeMounter,
181-
Exec: fakeExec,
182-
}
183-
}
184-
185146
func (gceDriver *GCEDriver) Run(endpoint string) {
186147
glog.Infof("Driver: %v", gceDriver.name)
187148

pkg/gce-pd-csi-driver/node.go

+15-147
Original file line numberDiff line numberDiff line change
@@ -17,48 +17,20 @@ package gceGCEDriver
1717
import (
1818
"fmt"
1919
"os"
20-
"path"
21-
"path/filepath"
22-
"strings"
2320
"sync"
2421

2522
csi "github.com/container-storage-interface/spec/lib/go/csi/v0"
2623
"github.com/golang/glog"
2724
"golang.org/x/net/context"
2825
"google.golang.org/grpc/codes"
2926
"google.golang.org/grpc/status"
30-
"k8s.io/apimachinery/pkg/util/sets"
31-
"k8s.io/kubernetes/pkg/util/mount"
32-
"k8s.io/utils/exec"
27+
mountmanager "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/mount-manager"
3328
utils "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/utils"
3429
)
3530

36-
const (
37-
diskByHostIdPath = "/host/dev/disk/by-id/"
38-
diskByIdPath = "/dev/disk/by-id/"
39-
diskGooglePrefix = "google-"
40-
diskScsiGooglePrefix = "scsi-0Google_PersistentDisk_"
41-
diskPartitionSuffix = "-part"
42-
diskSDPath = "/host/dev/sd"
43-
diskSDPattern = "/host/dev/sd*"
44-
// How many times to retry for a consistent read of /proc/mounts.
45-
maxListTries = 3
46-
// Number of fields per line in /proc/mounts as per the fstab man page.
47-
expectedNumFieldsPerLine = 6
48-
// Location of the mount file to use
49-
procMountsPath = "/proc/mounts"
50-
// Location of the mountinfo file
51-
procMountInfoPath = "/proc/self/mountinfo"
52-
// 'fsck' found errors and corrected them
53-
fsckErrorsCorrected = 1
54-
// 'fsck' found errors but exited without correcting them
55-
fsckErrorsUncorrected = 4
56-
defaultMountCommand = "mount"
57-
)
58-
5931
type GCENodeServer struct {
6032
Driver *GCEDriver
61-
Mounter mount.SafeFormatAndMount
33+
Mounter mountmanager.MountManager
6234
// TODO: Only lock mutually exclusive calls and make locking more fine grained
6335
mux sync.Mutex
6436
}
@@ -89,7 +61,7 @@ func (ns *GCENodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePub
8961
return nil, status.Error(codes.InvalidArgument, "NodePublishVolume Volume Capability must be provided")
9062
}
9163

92-
notMnt, err := ns.Mounter.IsLikelyNotMountPoint(targetPath)
64+
notMnt, err := ns.Mounter.GetSafeMounter().Interface.IsLikelyNotMountPoint(targetPath)
9365
if err != nil && !os.IsNotExist(err) {
9466
glog.Errorf("cannot validate mount point: %s %v", targetPath, err)
9567
return nil, err
@@ -99,7 +71,7 @@ func (ns *GCENodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePub
9971
return nil, nil
10072
}
10173

102-
if err := ns.Mounter.MakeDir(targetPath); err != nil {
74+
if err := ns.Mounter.GetSafeMounter().Interface.MakeDir(targetPath); err != nil {
10375
glog.Errorf("mkdir failed on disk %s (%v)", targetPath, err)
10476
return nil, err
10577
}
@@ -110,19 +82,19 @@ func (ns *GCENodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePub
11082
options = append(options, "ro")
11183
}
11284

113-
err = ns.Mounter.Mount(stagingTargetPath, targetPath, "ext4", options)
85+
err = ns.Mounter.GetSafeMounter().Interface.Mount(stagingTargetPath, targetPath, "ext4", options)
11486
if err != nil {
115-
notMnt, mntErr := ns.Mounter.IsLikelyNotMountPoint(targetPath)
87+
notMnt, mntErr := ns.Mounter.GetSafeMounter().Interface.IsLikelyNotMountPoint(targetPath)
11688
if mntErr != nil {
11789
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
11890
return nil, status.Error(codes.Internal, fmt.Sprintf("TODO: %v", err))
11991
}
12092
if !notMnt {
121-
if mntErr = ns.Mounter.Unmount(targetPath); mntErr != nil {
93+
if mntErr = ns.Mounter.GetSafeMounter().Interface.Unmount(targetPath); mntErr != nil {
12294
glog.Errorf("Failed to unmount: %v", mntErr)
12395
return nil, status.Error(codes.Internal, fmt.Sprintf("TODO: %v", err))
12496
}
125-
notMnt, mntErr := ns.Mounter.IsLikelyNotMountPoint(targetPath)
97+
notMnt, mntErr := ns.Mounter.GetSafeMounter().Interface.IsLikelyNotMountPoint(targetPath)
12698
if mntErr != nil {
12799
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
128100
return nil, status.Error(codes.Internal, fmt.Sprintf("TODO: %v", err))
@@ -158,7 +130,7 @@ func (ns *GCENodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeU
158130

159131
// TODO: Check volume still exists
160132

161-
err := ns.Mounter.Unmount(targetPath)
133+
err := ns.Mounter.GetSafeMounter().Interface.Unmount(targetPath)
162134
if err != nil {
163135
return nil, status.Error(codes.Internal, fmt.Sprintf("Unmount failed: %v\nUnmounting arguments: %s\n", err, targetPath))
164136
}
@@ -196,8 +168,8 @@ func (ns *GCENodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStage
196168
// TODO: Get real partitions
197169
partition := ""
198170

199-
devicePaths := getDiskByIdPaths(volumeName, partition)
200-
devicePath, err := verifyDevicePath(devicePaths)
171+
devicePaths := ns.Mounter.GetDiskByIdPaths(volumeName, partition)
172+
devicePath, err := ns.Mounter.VerifyDevicePath(devicePaths)
201173

202174
if err != nil {
203175
return nil, status.Error(codes.Internal, fmt.Sprintf("Error verifying GCE PD (%q) is attached: %v", volumeName, err))
@@ -209,10 +181,10 @@ func (ns *GCENodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStage
209181
glog.Infof("Successfully found attached GCE PD %q at device path %s.", volumeName, devicePath)
210182

211183
// Part 2: Check if mount already exists at targetpath
212-
notMnt, err := ns.Mounter.IsLikelyNotMountPoint(stagingTargetPath)
184+
notMnt, err := ns.Mounter.GetSafeMounter().Interface.IsLikelyNotMountPoint(stagingTargetPath)
213185
if err != nil {
214186
if os.IsNotExist(err) {
215-
if err := ns.Mounter.MakeDir(stagingTargetPath); err != nil {
187+
if err := ns.Mounter.GetSafeMounter().Interface.MakeDir(stagingTargetPath); err != nil {
216188
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to create directory (%q): %v", stagingTargetPath, err))
217189
}
218190
notMnt = true
@@ -246,7 +218,7 @@ func (ns *GCENodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStage
246218
return nil, status.Error(codes.Unimplemented, fmt.Sprintf("Block volume support is not yet implemented"))
247219
}
248220

249-
err = ns.Mounter.FormatAndMount(devicePath, stagingTargetPath, fstype, options)
221+
err = ns.Mounter.GetSafeMounter().FormatAndMount(devicePath, stagingTargetPath, fstype, options)
250222
if err != nil {
251223
return nil, status.Error(codes.Internal,
252224
fmt.Sprintf("Failed to format and mount device from (%q) to (%q) with fstype (%q) and options (%q): %v",
@@ -270,7 +242,7 @@ func (ns *GCENodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUns
270242
return nil, status.Error(codes.InvalidArgument, "NodeUnstageVolume Staging Target Path must be provided")
271243
}
272244

273-
err := ns.Mounter.Unmount(stagingTargetPath)
245+
err := ns.Mounter.GetSafeMounter().Interface.Unmount(stagingTargetPath)
274246
if err != nil {
275247
return nil, status.Error(codes.Internal, fmt.Sprintf("NodeUnstageVolume failed to unmount at path %s: %v", stagingTargetPath, err))
276248
}
@@ -305,107 +277,3 @@ func (ns *GCENodeServer) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRe
305277
}
306278
return resp, nil
307279
}
308-
309-
// Returns list of all /dev/disk/by-id/* paths for given PD.
310-
func getDiskByIdPaths(pdName string, partition string) []string {
311-
devicePaths := []string{
312-
path.Join(diskByIdPath, diskGooglePrefix+pdName),
313-
path.Join(diskByIdPath, diskScsiGooglePrefix+pdName),
314-
path.Join(diskByHostIdPath, diskScsiGooglePrefix+pdName),
315-
path.Join(diskByHostIdPath, diskScsiGooglePrefix+pdName),
316-
}
317-
318-
if partition != "" {
319-
for i, path := range devicePaths {
320-
devicePaths[i] = path + diskPartitionSuffix + partition
321-
}
322-
}
323-
324-
return devicePaths
325-
}
326-
327-
// Returns the first path that exists, or empty string if none exist.
328-
func verifyDevicePath(devicePaths []string) (string, error) {
329-
sdBefore, err := filepath.Glob(diskSDPattern)
330-
if err != nil {
331-
// Seeing this error means that the diskSDPattern is malformed.
332-
glog.Errorf("Error filepath.Glob(\"%s\"): %v\r\n", diskSDPattern, err)
333-
}
334-
sdBeforeSet := sets.NewString(sdBefore...)
335-
// TODO: Remove this udevadm stuff. Not applicable because can't access /dev/sd* from container
336-
if err := udevadmChangeToNewDrives(sdBeforeSet); err != nil {
337-
// udevadm errors should not block disk detachment, log and continue
338-
glog.Errorf("udevadmChangeToNewDrives failed with: %v", err)
339-
}
340-
341-
for _, path := range devicePaths {
342-
if pathExists, err := pathExists(path); err != nil {
343-
return "", fmt.Errorf("Error checking if path exists: %v", err)
344-
} else if pathExists {
345-
return path, nil
346-
}
347-
}
348-
349-
return "", nil
350-
}
351-
352-
// Triggers the application of udev rules by calling "udevadm trigger
353-
// --action=change" for newly created "/dev/sd*" drives (exist only in
354-
// after set). This is workaround for Issue #7972. Once the underlying
355-
// issue has been resolved, this may be removed.
356-
func udevadmChangeToNewDrives(sdBeforeSet sets.String) error {
357-
sdAfter, err := filepath.Glob(diskSDPattern)
358-
if err != nil {
359-
return fmt.Errorf("Error filepath.Glob(\"%s\"): %v\r\n", diskSDPattern, err)
360-
}
361-
362-
for _, sd := range sdAfter {
363-
if !sdBeforeSet.Has(sd) {
364-
return udevadmChangeToDrive(sd)
365-
}
366-
}
367-
368-
return nil
369-
}
370-
371-
// Calls "udevadm trigger --action=change" on the specified drive.
372-
// drivePath must be the block device path to trigger on, in the format "/dev/sd*", or a symlink to it.
373-
// This is workaround for Issue #7972. Once the underlying issue has been resolved, this may be removed.
374-
func udevadmChangeToDrive(drivePath string) error {
375-
glog.V(5).Infof("udevadmChangeToDrive: drive=%q", drivePath)
376-
377-
// Evaluate symlink, if any
378-
drive, err := filepath.EvalSymlinks(drivePath)
379-
if err != nil {
380-
return fmt.Errorf("udevadmChangeToDrive: filepath.EvalSymlinks(%q) failed with %v.", drivePath, err)
381-
}
382-
glog.V(5).Infof("udevadmChangeToDrive: symlink path is %q", drive)
383-
384-
// Check to make sure input is "/dev/sd*"
385-
if !strings.Contains(drive, diskSDPath) {
386-
return fmt.Errorf("udevadmChangeToDrive: expected input in the form \"%s\" but drive is %q.", diskSDPattern, drive)
387-
}
388-
389-
// Call "udevadm trigger --action=change --property-match=DEVNAME=/dev/sd..."
390-
_, err = exec.New().Command(
391-
"udevadm",
392-
"trigger",
393-
"--action=change",
394-
fmt.Sprintf("--property-match=DEVNAME=%s", drive)).CombinedOutput()
395-
if err != nil {
396-
return fmt.Errorf("udevadmChangeToDrive: udevadm trigger failed for drive %q with %v.", drive, err)
397-
}
398-
return nil
399-
}
400-
401-
// PathExists returns true if the specified path exists.
402-
func pathExists(path string) (bool, error) {
403-
_, err := os.Stat(path)
404-
if err == nil {
405-
return true, nil
406-
} else if os.IsNotExist(err) {
407-
return false, nil
408-
} else {
409-
return false, err
410-
}
411-
}

0 commit comments

Comments
 (0)