diff --git a/Dockerfile b/Dockerfile index aa6464e644..2c79af3489 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM golang:1.15.6 AS builder +FROM golang:1.16 AS builder WORKDIR /go/src/github.com/kubernetes-sigs/aws-ebs-csi-driver COPY . . RUN make diff --git a/Dockerfile.windows b/Dockerfile.windows new file mode 100644 index 0000000000..420096dd38 --- /dev/null +++ b/Dockerfile.windows @@ -0,0 +1,23 @@ +# Copyright 2019 The Kubernetes Authors. +# +# 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. + +FROM --platform=$BUILDPLATFORM golang:1.16 AS builder +WORKDIR /go/src/github.com/kubernetes-sigs/aws-ebs-csi-driver +COPY . . +RUN make bin/aws-ebs-csi-driver.exe + +FROM mcr.microsoft.com/windows/servercore:1809 +COPY --from=builder /go/src/github.com/kubernetes-sigs/aws-ebs-csi-driver/bin/aws-ebs-csi-driver.exe /aws-ebs-csi-driver.exe + +ENTRYPOINT ["/aws-ebs-csi-driver.exe"] diff --git a/Makefile b/Makefile index 91781e98c7..743eabc945 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,10 @@ GOBIN=$(shell pwd)/bin bin/aws-ebs-csi-driver: | bin CGO_ENABLED=0 GOOS=linux go build -mod=vendor -ldflags ${LDFLAGS} -o bin/aws-ebs-csi-driver ./cmd/ +.PHONY: bin/aws-ebs-csi-driver.exe +bin/aws-ebs-csi-driver.exe: | bin + CGO_ENABLED=0 GOOS=windows go build -mod=vendor -ldflags ${LDFLAGS} -o bin/aws-ebs-csi-driver.exe ./cmd/ + bin /tmp/helm /tmp/kubeval: @mkdir -p $@ diff --git a/pkg/driver/mount.go b/pkg/driver/mount.go index f1ce7b48c8..0e3e938a0b 100644 --- a/pkg/driver/mount.go +++ b/pkg/driver/mount.go @@ -17,199 +17,40 @@ limitations under the License. package driver import ( - "fmt" - "k8s.io/klog" - "os" - "strconv" - "strings" - "github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/mounter" mountutils "k8s.io/mount-utils" - utilexec "k8s.io/utils/exec" ) +type mountInterface = mountutils.Interface + +// Mounter is the interface implemented by NodeMounter. +// A mix & match of functions defined in upstream libraries. (FormatAndMount +// from struct SafeFormatAndMount, PathExists from an old edition of +// mount.Interface). Define it explicitly so that it can be mocked and to +// insulate from oft-changing upstream interfaces/structs type Mounter interface { - // Implemented by NodeMounter.SafeFormatAndMount - mountutils.Interface - FormatAndMount(source string, target string, fstype string, options []string) error + mountInterface - // Implemented by NodeMounter.SafeFormatAndMount.Exec - // TODO this won't make sense on Windows with csi-proxy - utilexec.Interface + FormatAndMount(source string, target string, fstype string, options []string) error - // Implemented by NodeMounter below GetDeviceNameFromMount(mountPath string) (string, int, error) - // TODO this won't make sense on Windows with csi-proxy MakeFile(path string) error MakeDir(path string) error PathExists(path string) (bool, error) NeedResize(devicePath string, deviceMountPath string) (bool, error) } +// NodeMounter implements Mounter. +// A superstruct of SafeFormatAndMount. type NodeMounter struct { - mountutils.SafeFormatAndMount - utilexec.Interface + *mountutils.SafeFormatAndMount } func newNodeMounter() (Mounter, error) { + // mounter.NewSafeMounter returns a SafeFormatAndMount safeMounter, err := mounter.NewSafeMounter() if err != nil { return nil, err } - return &NodeMounter{*safeMounter, safeMounter.Exec}, nil -} - -// GetDeviceNameFromMount returns the volume ID for a mount path. -func (m NodeMounter) GetDeviceNameFromMount(mountPath string) (string, int, error) { - return mountutils.GetDeviceNameFromMount(m, mountPath) -} - -// This function is mirrored in ./sanity_test.go to make sure sanity test covered this block of code -// Please mirror the change to func MakeFile in ./sanity_test.go -func (m *NodeMounter) MakeFile(path string) error { - f, err := os.OpenFile(path, os.O_CREATE, os.FileMode(0644)) - if err != nil { - if !os.IsExist(err) { - return err - } - } - if err = f.Close(); err != nil { - return err - } - return nil -} - -// This function is mirrored in ./sanity_test.go to make sure sanity test covered this block of code -// Please mirror the change to func MakeFile in ./sanity_test.go -func (m *NodeMounter) MakeDir(path string) error { - err := os.MkdirAll(path, os.FileMode(0755)) - if err != nil { - if !os.IsExist(err) { - return err - } - } - return nil -} - -// This function is mirrored in ./sanity_test.go to make sure sanity test covered this block of code -// Please mirror the change to func MakeFile in ./sanity_test.go -func (m *NodeMounter) PathExists(path string) (bool, error) { - return mountutils.PathExists(path) -} - -//TODO: use common util from vendor kubernetes/mount-util -func (m *NodeMounter) NeedResize(devicePath string, deviceMountPath string) (bool, error) { - // TODO(xiangLi) resize fs size on formatted file system following this PR https://github.com/kubernetes/kubernetes/pull/99223 - // Port the in-tree un-released change first, need to remove after in-tree release - deviceSize, err := m.getDeviceSize(devicePath) - if err != nil { - return false, err - } - var fsSize, blockSize uint64 - format, err := m.SafeFormatAndMount.GetDiskFormat(devicePath) - if err != nil { - formatErr := fmt.Errorf("ResizeFS.Resize - error checking format for device %s: %v", devicePath, err) - return false, formatErr - } - - // If disk has no format, there is no need to resize the disk because mkfs.* - // by default will use whole disk anyways. - if format == "" { - return false, nil - } - - klog.V(3).Infof("ResizeFs.needResize - checking mounted volume %s", devicePath) - switch format { - case "ext3", "ext4": - blockSize, fsSize, err = m.getExtSize(devicePath) - klog.V(5).Infof("Ext size: filesystem size=%d, block size=%d", fsSize, blockSize) - case "xfs": - blockSize, fsSize, err = m.getXFSSize(deviceMountPath) - klog.V(5).Infof("Xfs size: filesystem size=%d, block size=%d, err=%v", fsSize, blockSize, err) - default: - klog.Errorf("Not able to parse given filesystem info. fsType: %s, will not resize", format) - return false, fmt.Errorf("Could not parse fs info on given filesystem format: %s. Supported fs types are: xfs, ext3, ext4", format) - } - if err != nil { - return false, err - } - // Tolerate one block difference, just in case of rounding errors somewhere. - klog.V(5).Infof("Volume %s: device size=%d, filesystem size=%d, block size=%d", devicePath, deviceSize, fsSize, blockSize) - if deviceSize <= fsSize+blockSize { - return false, nil - } - return true, nil -} -func (m *NodeMounter) getDeviceSize(devicePath string) (uint64, error) { - output, err := m.SafeFormatAndMount.Exec.Command("blockdev", "--getsize64", devicePath).CombinedOutput() - outStr := strings.TrimSpace(string(output)) - if err != nil { - return 0, fmt.Errorf("failed to read size of device %s: %s: %s", devicePath, err, outStr) - } - size, err := strconv.ParseUint(outStr, 10, 64) - if err != nil { - return 0, fmt.Errorf("failed to parse size of device %s %s: %s", devicePath, outStr, err) - } - return size, nil -} - -func (m *NodeMounter) getExtSize(devicePath string) (uint64, uint64, error) { - output, err := m.SafeFormatAndMount.Exec.Command("dumpe2fs", "-h", devicePath).CombinedOutput() - if err != nil { - return 0, 0, fmt.Errorf("failed to read size of filesystem on %s: %s: %s", devicePath, err, string(output)) - } - - blockSize, blockCount, _ := m.parseFsInfoOutput(string(output), ":", "block size", "block count") - - if blockSize == 0 { - return 0, 0, fmt.Errorf("could not find block size of device %s", devicePath) - } - if blockCount == 0 { - return 0, 0, fmt.Errorf("could not find block count of device %s", devicePath) - } - return blockSize, blockSize * blockCount, nil -} - -func (m *NodeMounter) getXFSSize(devicePath string) (uint64, uint64, error) { - output, err := m.SafeFormatAndMount.Exec.Command("xfs_io", "-c", "statfs", devicePath).CombinedOutput() - if err != nil { - return 0, 0, fmt.Errorf("failed to read size of filesystem on %s: %s: %s", devicePath, err, string(output)) - } - - blockSize, blockCount, _ := m.parseFsInfoOutput(string(output), "=", "geom.bsize", "geom.datablocks") - - if blockSize == 0 { - return 0, 0, fmt.Errorf("could not find block size of device %s", devicePath) - } - if blockCount == 0 { - return 0, 0, fmt.Errorf("could not find block count of device %s", devicePath) - } - return blockSize, blockSize * blockCount, nil -} - -func (m *NodeMounter) parseFsInfoOutput(cmdOutput string, spliter string, blockSizeKey string, blockCountKey string) (uint64, uint64, error) { - lines := strings.Split(cmdOutput, "\n") - var blockSize, blockCount uint64 - var err error - - for _, line := range lines { - tokens := strings.Split(line, spliter) - if len(tokens) != 2 { - continue - } - key, value := strings.ToLower(strings.TrimSpace(tokens[0])), strings.ToLower(strings.TrimSpace(tokens[1])) - if key == blockSizeKey { - blockSize, err = strconv.ParseUint(value, 10, 64) - if err != nil { - return 0, 0, fmt.Errorf("failed to parse block size %s: %s", value, err) - } - } - if key == blockCountKey { - blockCount, err = strconv.ParseUint(value, 10, 64) - if err != nil { - return 0, 0, fmt.Errorf("failed to parse block count %s: %s", value, err) - } - } - } - return blockSize, blockCount, err + return &NodeMounter{safeMounter}, nil } diff --git a/pkg/driver/mount_linux.go b/pkg/driver/mount_linux.go new file mode 100644 index 0000000000..ea4427c549 --- /dev/null +++ b/pkg/driver/mount_linux.go @@ -0,0 +1,184 @@ +// +build linux + +/* +Copyright 2019 The Kubernetes Authors. + +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 driver + +import ( + "fmt" + "k8s.io/klog" + "os" + "strconv" + "strings" + + mountutils "k8s.io/mount-utils" +) + +// GetDeviceNameFromMount returns the volume ID for a mount path. +func (m NodeMounter) GetDeviceNameFromMount(mountPath string) (string, int, error) { + return mountutils.GetDeviceNameFromMount(m, mountPath) +} + +// This function is mirrored in ./sanity_test.go to make sure sanity test covered this block of code +// Please mirror the change to func MakeFile in ./sanity_test.go +func (m *NodeMounter) MakeFile(path string) error { + f, err := os.OpenFile(path, os.O_CREATE, os.FileMode(0644)) + if err != nil { + if !os.IsExist(err) { + return err + } + } + if err = f.Close(); err != nil { + return err + } + return nil +} + +// This function is mirrored in ./sanity_test.go to make sure sanity test covered this block of code +// Please mirror the change to func MakeFile in ./sanity_test.go +func (m *NodeMounter) MakeDir(path string) error { + err := os.MkdirAll(path, os.FileMode(0755)) + if err != nil { + if !os.IsExist(err) { + return err + } + } + return nil +} + +// This function is mirrored in ./sanity_test.go to make sure sanity test covered this block of code +// Please mirror the change to func MakeFile in ./sanity_test.go +func (m *NodeMounter) PathExists(path string) (bool, error) { + return mountutils.PathExists(path) +} + +//TODO: use common util from vendor kubernetes/mount-util +func (m *NodeMounter) NeedResize(devicePath string, deviceMountPath string) (bool, error) { + // TODO(xiangLi) resize fs size on formatted file system following this PR https://github.com/kubernetes/kubernetes/pull/99223 + // Port the in-tree un-released change first, need to remove after in-tree release + deviceSize, err := m.getDeviceSize(devicePath) + if err != nil { + return false, err + } + var fsSize, blockSize uint64 + format, err := m.SafeFormatAndMount.GetDiskFormat(devicePath) + if err != nil { + formatErr := fmt.Errorf("ResizeFS.Resize - error checking format for device %s: %v", devicePath, err) + return false, formatErr + } + + // If disk has no format, there is no need to resize the disk because mkfs.* + // by default will use whole disk anyways. + if format == "" { + return false, nil + } + + klog.V(3).Infof("ResizeFs.needResize - checking mounted volume %s", devicePath) + switch format { + case "ext3", "ext4": + blockSize, fsSize, err = m.getExtSize(devicePath) + klog.V(5).Infof("Ext size: filesystem size=%d, block size=%d", fsSize, blockSize) + case "xfs": + blockSize, fsSize, err = m.getXFSSize(deviceMountPath) + klog.V(5).Infof("Xfs size: filesystem size=%d, block size=%d, err=%v", fsSize, blockSize, err) + default: + klog.Errorf("Not able to parse given filesystem info. fsType: %s, will not resize", format) + return false, fmt.Errorf("Could not parse fs info on given filesystem format: %s. Supported fs types are: xfs, ext3, ext4", format) + } + if err != nil { + return false, err + } + // Tolerate one block difference, just in case of rounding errors somewhere. + klog.V(5).Infof("Volume %s: device size=%d, filesystem size=%d, block size=%d", devicePath, deviceSize, fsSize, blockSize) + if deviceSize <= fsSize+blockSize { + return false, nil + } + return true, nil +} +func (m *NodeMounter) getDeviceSize(devicePath string) (uint64, error) { + output, err := m.SafeFormatAndMount.Exec.Command("blockdev", "--getsize64", devicePath).CombinedOutput() + outStr := strings.TrimSpace(string(output)) + if err != nil { + return 0, fmt.Errorf("failed to read size of device %s: %s: %s", devicePath, err, outStr) + } + size, err := strconv.ParseUint(outStr, 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to parse size of device %s %s: %s", devicePath, outStr, err) + } + return size, nil +} + +func (m *NodeMounter) getExtSize(devicePath string) (uint64, uint64, error) { + output, err := m.SafeFormatAndMount.Exec.Command("dumpe2fs", "-h", devicePath).CombinedOutput() + if err != nil { + return 0, 0, fmt.Errorf("failed to read size of filesystem on %s: %s: %s", devicePath, err, string(output)) + } + + blockSize, blockCount, _ := m.parseFsInfoOutput(string(output), ":", "block size", "block count") + + if blockSize == 0 { + return 0, 0, fmt.Errorf("could not find block size of device %s", devicePath) + } + if blockCount == 0 { + return 0, 0, fmt.Errorf("could not find block count of device %s", devicePath) + } + return blockSize, blockSize * blockCount, nil +} + +func (m *NodeMounter) getXFSSize(devicePath string) (uint64, uint64, error) { + output, err := m.SafeFormatAndMount.Exec.Command("xfs_io", "-c", "statfs", devicePath).CombinedOutput() + if err != nil { + return 0, 0, fmt.Errorf("failed to read size of filesystem on %s: %s: %s", devicePath, err, string(output)) + } + + blockSize, blockCount, _ := m.parseFsInfoOutput(string(output), "=", "geom.bsize", "geom.datablocks") + + if blockSize == 0 { + return 0, 0, fmt.Errorf("could not find block size of device %s", devicePath) + } + if blockCount == 0 { + return 0, 0, fmt.Errorf("could not find block count of device %s", devicePath) + } + return blockSize, blockSize * blockCount, nil +} + +func (m *NodeMounter) parseFsInfoOutput(cmdOutput string, spliter string, blockSizeKey string, blockCountKey string) (uint64, uint64, error) { + lines := strings.Split(cmdOutput, "\n") + var blockSize, blockCount uint64 + var err error + + for _, line := range lines { + tokens := strings.Split(line, spliter) + if len(tokens) != 2 { + continue + } + key, value := strings.ToLower(strings.TrimSpace(tokens[0])), strings.ToLower(strings.TrimSpace(tokens[1])) + if key == blockSizeKey { + blockSize, err = strconv.ParseUint(value, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse block size %s: %s", value, err) + } + } + if key == blockCountKey { + blockCount, err = strconv.ParseUint(value, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse block count %s: %s", value, err) + } + } + } + return blockSize, blockCount, err +} diff --git a/pkg/driver/mount_test.go b/pkg/driver/mount_test.go index 18354e9442..32eb5a95f0 100644 --- a/pkg/driver/mount_test.go +++ b/pkg/driver/mount_test.go @@ -234,7 +234,7 @@ Journal checksum: 0xb7df3c6e Interface: mount.New(""), Exec: &fexec, } - fakeMounter := NodeMounter{safe, &fexec} + fakeMounter := NodeMounter{&safe} var blockSize uint64 var fsSize uint64 @@ -296,9 +296,9 @@ func TestNeedResize(t *testing.T) { } safe := mount.SafeFormatAndMount{ Interface: mount.New(""), - Exec: utilexec.New(), + Exec: &fexec, } - fakeMounter := NodeMounter{safe, &fexec} + fakeMounter := NodeMounter{&safe} needResize, err := fakeMounter.NeedResize(test.devicePath, test.deviceMountPath) if needResize != test.expectResult { diff --git a/pkg/driver/mount_windows.go b/pkg/driver/mount_windows.go new file mode 100644 index 0000000000..916672a136 --- /dev/null +++ b/pkg/driver/mount_windows.go @@ -0,0 +1,99 @@ +// +build windows + +/* +Copyright 2019 The Kubernetes Authors. + +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 driver + +import ( + "fmt" + "regexp" + + "github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/mounter" +) + +func (m NodeMounter) FormatAndMount(source string, target string, fstype string, options []string) error { + proxyMounter, ok := m.SafeFormatAndMount.Interface.(*mounter.CSIProxyMounter) + if !ok { + return fmt.Errorf("failed to cast mounter to csi proxy mounter") + } + return proxyMounter.FormatAndMount(source, target, fstype, options) +} + +// GetDeviceNameFromMount returns the volume ID for a mount path. +// The ref count returned is always 1 or 0 because csi-proxy doesn't provide a +// way to determine the actual ref count (as opposed to Linux where the mount +// table gets read). In practice this shouldn't matter, as in the NodeStage +// case the ref count is ignored and in the NodeUnstage case, the ref count +// being >1 is just a warning. +// Command to determine ref count would be something like: +// Get-Volume -UniqueId "\\?\Volume{7c3da0c1-0000-0000-0000-010000000000}\" | Get-Partition | Select AccessPaths +func (m NodeMounter) GetDeviceNameFromMount(mountPath string) (string, int, error) { + proxyMounter, ok := m.SafeFormatAndMount.Interface.(*mounter.CSIProxyMounter) + if !ok { + return "", 0, fmt.Errorf("failed to cast mounter to csi proxy mounter") + } + deviceName, err := proxyMounter.GetDeviceNameFromMount(mountPath, "") + if err != nil { + // HACK change csi-proxy behavior instead of relying on fragile internal + // implementation details! + // if err contains '"(Get-Item...).Target, output: , error: ' then the + // internal Get-Item cmdlet didn't fail but no item/device was found at the + // path so we should return empty string and nil error just like the Linux + // implementation would. + pattern := `(Get-Item -Path \S+).Target, output: , error: ` + matched, matchErr := regexp.MatchString(pattern, err.Error()) + if matched { + return "", 0, nil + } + err = fmt.Errorf("error getting device name from mount: %v", err) + if matchErr != nil { + err = fmt.Errorf("%v, and error matching pattern %q: %v", err, pattern, matchErr) + } + return "", 0, err + } + return deviceName, 1, nil +} + +func (m *NodeMounter) MakeFile(path string) error { + proxyMounter, ok := m.SafeFormatAndMount.Interface.(*mounter.CSIProxyMounter) + if !ok { + return fmt.Errorf("failed to cast mounter to csi proxy mounter") + } + return proxyMounter.MakeFile(path) +} + +func (m *NodeMounter) MakeDir(path string) error { + proxyMounter, ok := m.SafeFormatAndMount.Interface.(*mounter.CSIProxyMounter) + if !ok { + return fmt.Errorf("failed to cast mounter to csi proxy mounter") + } + return proxyMounter.MakeDir(path) +} + +func (m *NodeMounter) PathExists(path string) (bool, error) { + proxyMounter, ok := m.SafeFormatAndMount.Interface.(*mounter.CSIProxyMounter) + if !ok { + return false, fmt.Errorf("failed to cast mounter to csi proxy mounter") + } + return proxyMounter.ExistsPath(path) +} + +func (m *NodeMounter) NeedResize(devicePath string, deviceMountPath string) (bool, error) { + // TODO this is called at NodeStage to ensure file system is the correct size + // Implement it to respect spec v1.4.0 https://github.com/container-storage-interface/spec/pull/452 + return false, nil +} diff --git a/pkg/driver/node.go b/pkg/driver/node.go index c844cc4416..641e16a732 100644 --- a/pkg/driver/node.go +++ b/pkg/driver/node.go @@ -218,7 +218,7 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol return nil, status.Errorf(codes.Internal, "Could not determine if volume %q (%q) need to be resized: %v", req.GetVolumeId(), source, err) } if needResize { - r := mountutils.NewResizeFs(d.mounter) + r := mountutils.NewResizeFs(d.mounter.(*NodeMounter).Exec) klog.V(2).Infof("Volume %s needs resizing", source) if _, err := r.Resize(source, target); err != nil { return nil, status.Errorf(codes.Internal, "Could not resize volume %q (%q): %v", volumeID, source, err) @@ -321,7 +321,7 @@ func (d *nodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV // TODO this won't make sense on Windows with csi-proxy args := []string{"-o", "source", "--noheadings", "--target", volumePath} - output, err := d.mounter.Command("findmnt", args...).Output() + output, err := d.mounter.(*NodeMounter).Exec.Command("findmnt", args...).Output() if err != nil { return nil, status.Errorf(codes.Internal, "Could not determine device path: %v", err) @@ -331,7 +331,7 @@ func (d *nodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV return nil, status.Errorf(codes.Internal, "Could not get valid device for mount path: %q", req.GetVolumePath()) } - r := mountutils.NewResizeFs(d.mounter) + r := mountutils.NewResizeFs(d.mounter.(*NodeMounter).Exec) // TODO: lock per volume ID to have some idempotency if _, err := r.Resize(devicePath, volumePath); err != nil { @@ -608,9 +608,8 @@ func (d *nodeService) nodePublishVolumeForFileSystem(req *csi.NodePublishVolumeR } } - klog.V(4).Infof("NodePublishVolume: creating dir %s", target) - if err := d.mounter.MakeDir(target); err != nil { - return status.Errorf(codes.Internal, "Could not create dir %q: %v", target, err) + if err := d.preparePublishTarget(target); err != nil { + return status.Errorf(codes.Internal, err.Error()) } fsType := mode.Mount.GetFsType() diff --git a/pkg/driver/node_linux.go b/pkg/driver/node_linux.go index 4c4a9d0955..a6efa9b305 100644 --- a/pkg/driver/node_linux.go +++ b/pkg/driver/node_linux.go @@ -26,7 +26,7 @@ import ( "strings" "golang.org/x/sys/unix" - "k8s.io/klog" + "k8s.io/klog/v2" ) // findDevicePath finds path of device and verifies its existence @@ -93,6 +93,14 @@ func findNvmeVolume(findName string) (device string, err error) { return resolved, nil } +func (d *nodeService) preparePublishTarget(target string) error { + klog.V(4).Infof("NodePublishVolume: creating dir %s", target) + if err := d.mounter.MakeDir(target); err != nil { + return fmt.Errorf("Could not create dir %q: %v", target, err) + } + return nil +} + // IsBlock checks if the given path is a block device func (d *nodeService) IsBlockDevice(fullPath string) (bool, error) { var st unix.Stat_t @@ -105,7 +113,7 @@ func (d *nodeService) IsBlockDevice(fullPath string) (bool, error) { } func (d *nodeService) getBlockSizeBytes(devicePath string) (int64, error) { - cmd := d.mounter.Command("blockdev", "--getsize64", devicePath) + cmd := d.mounter.(*NodeMounter).Exec.Command("blockdev", "--getsize64", devicePath) output, err := cmd.Output() if err != nil { return -1, fmt.Errorf("error when getting size of block volume at path %s: output: %s, err: %v", devicePath, string(output), err) diff --git a/pkg/driver/node_windows.go b/pkg/driver/node_windows.go new file mode 100644 index 0000000000..715a403a72 --- /dev/null +++ b/pkg/driver/node_windows.go @@ -0,0 +1,98 @@ +// +build windows + +/* +Copyright 2019 The Kubernetes Authors. + +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 driver + +import ( + "context" + "errors" + "fmt" + "strings" + + diskapi "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1beta2" + diskclient "github.com/kubernetes-csi/csi-proxy/client/groups/disk/v1beta2" + "github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/mounter" + "k8s.io/klog" +) + +// findDevicePath finds disk number of device +// https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-windows-volumes.html#list-nvme-powershell +func (d *nodeService) findDevicePath(devicePath, volumeID, _ string) (string, error) { + + diskClient, err := diskclient.NewClient() + if err != nil { + return "", fmt.Errorf("error creating csi-proxy disk client: %q", err) + } + defer diskClient.Close() + + response, err := diskClient.ListDiskIDs(context.TODO(), &diskapi.ListDiskIDsRequest{}) + if err != nil { + return "", fmt.Errorf("error listing disk ids: %q", err) + } + + diskIDs := response.GetDiskIDs() + + foundDiskNumber := "" + for diskNumber, diskID := range diskIDs { + serialNumber := diskID.Identifiers["serialNumber"] + cleanVolumeID := strings.ReplaceAll(volumeID, "-", "") + if strings.Contains(serialNumber, cleanVolumeID) { + foundDiskNumber = diskNumber + break + } + } + + if foundDiskNumber == "" { + return "", fmt.Errorf("disk number for device path %q volume id %q not found", devicePath, volumeID) + } + + return foundDiskNumber, nil +} + +func (d *nodeService) preparePublishTarget(target string) error { + // On Windows, Mount will create the parent of target and mklink (create a symbolic link) at target later, so don't create a + // directory at target now. Otherwise mklink will error: "Cannot create a file when that file already exists". + // Instead, delete the target if it already exists (like if it was created by kubelet <1.20) + // https://github.com/kubernetes/kubernetes/pull/88759 + klog.V(4).Infof("NodePublishVolume: removing dir %s", target) + exists, err := d.mounter.PathExists(target) + if err != nil { + return fmt.Errorf("error checking path %q exists: %v", target, err) + } + + proxyMounter, ok := (d.mounter.(*NodeMounter)).SafeFormatAndMount.Interface.(*mounter.CSIProxyMounter) + if !ok { + return fmt.Errorf("failed to cast mounter to csi proxy mounter") + } + + if exists { + if err := proxyMounter.Rmdir(target); err != nil { + return fmt.Errorf("error Rmdir target %q: %v", target, err) + } + } + return nil +} + +// IsBlock checks if the given path is a block device +func (d *nodeService) IsBlockDevice(fullPath string) (bool, error) { + return false, errors.New("unsupported") +} + +func (d *nodeService) getBlockSizeBytes(devicePath string) (int64, error) { + return 0, errors.New("unsupported") +}