Skip to content

Support for identifying device paths for attached volumes on Vmware and XenServer #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: increase-pod-affinity-leniency
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/cloudstack-csi-driver/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ RUN apk add --no-cache \
# blkid, mount and umount are required by k8s.io/mount-utils \
blkid \
mount \
umount
umount \
# Provides udevadm for device path detection
udev

COPY ./bin/cloudstack-csi-driver /cloudstack-csi-driver
ENTRYPOINT ["/cloudstack-csi-driver"]
6 changes: 6 additions & 0 deletions deploy/k8s/node-daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ spec:
mountPath: /dev
- name: cloud-init-dir
mountPath: /run/cloud-init/
- name: sys-dir
mountPath: /sys
# Comment the above 2 lines and uncomment the next 2 lines for Ignition support
# - name: ignition-dir
# mountPath: /run/metadata
Expand Down Expand Up @@ -177,6 +179,10 @@ spec:
hostPath:
path: /run/cloud-init/
type: Directory
- name: sys-dir
hostPath:
path: /sys
type: Directory
# Comment the above 4 lines and uncomment the next 4 lines for Ignition support
# - name: ignition-dir
# hostPath:
Expand Down
161 changes: 158 additions & 3 deletions pkg/mount/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ func (m *mounter) GetBlockSizeBytes(devicePath string) (int64, error) {

func (m *mounter) GetDevicePath(ctx context.Context, volumeID string) (string, error) {
backoff := wait.Backoff{
Duration: 1 * time.Second,
Factor: 1.1,
Steps: 15,
Duration: 2 * time.Second,
Factor: 1.5,
Steps: 20,
}

var devicePath string
Expand Down Expand Up @@ -111,6 +111,24 @@ func (m *mounter) GetDevicePath(ctx context.Context, volumeID string) (string, e
}

func (m *mounter) getDevicePathBySerialID(volumeID string) (string, error) {
// First try XenServer device paths
xenDevicePath, err := m.getDevicePathForXenServer(volumeID)
if err != nil {
fmt.Printf("Failed to get VMware device path: %v\n", err)
}
if xenDevicePath != "" {
return xenDevicePath, nil
}

// Try VMware device paths
vmwareDevicePath, err := m.getDevicePathForVMware(volumeID)
if err != nil {
fmt.Printf("Failed to get VMware device path: %v\n", err)
}
if vmwareDevicePath != "" {
return vmwareDevicePath, nil
}
// Fall back to standard device paths (for KVM)
sourcePathPrefixes := []string{"virtio-", "scsi-", "scsi-0QEMU_QEMU_HARDDISK_"}
serial := diskUUIDToSerial(volumeID)
for _, prefix := range sourcePathPrefixes {
Expand All @@ -120,13 +138,150 @@ func (m *mounter) getDevicePathBySerialID(volumeID string) (string, error) {
return source, nil
}
if !os.IsNotExist(err) {
fmt.Printf("Not found: %s\n", err.Error())
return "", err
}
}

return "", nil
}

func (m *mounter) getDevicePathForXenServer(volumeID string) (string, error) {
for i := 'b'; i <= 'z'; i++ {
devicePath := fmt.Sprintf("/dev/xvd%c", i)
fmt.Printf("Checking XenServer device path: %s\n", devicePath)

if _, err := os.Stat(devicePath); err == nil {
isBlock, err := m.IsBlockDevice(devicePath)
if err == nil && isBlock {
if m.verifyXenServerDevice(devicePath, volumeID) {
fmt.Printf("Found and verified XenServer device: %s\n", devicePath)
return devicePath, nil
}
}
}
}
return "", fmt.Errorf("device not found for volume %s", volumeID)
}

func (m *mounter) verifyXenServerDevice(devicePath string, volumeID string) bool {
size, err := m.GetBlockSizeBytes(devicePath)
if err != nil {
fmt.Printf("Failed to get device size: %v\n", err)
return false
}
fmt.Printf("Device size: %d bytes\n", size)

mounted, err := m.isDeviceMounted(devicePath)
if err != nil {
fmt.Printf("Failed to check if device is mounted: %v\n", err)
return false
}
if mounted {
fmt.Printf("Device is already mounted: %s\n", devicePath)
return false
}

props, err := m.getDeviceProperties(devicePath)
if err != nil {
fmt.Printf("Failed to get device properties: %v\n", err)
return false
}
fmt.Printf("Device properties: %v\n", props)

return true
}

func (m *mounter) getDevicePathForVMware(volumeID string) (string, error) {
// Loop through /dev/sdb to /dev/sdz (/dev/sda -> the root disk)
for i := 'b'; i <= 'z'; i++ {
devicePath := fmt.Sprintf("/dev/sd%c", i)
fmt.Printf("Checking VMware device path: %s\n", devicePath)

if _, err := os.Stat(devicePath); err == nil {
isBlock, err := m.IsBlockDevice(devicePath)
if err == nil && isBlock {
// Use the same verification as for XenServer
if m.verifyVMwareDevice(devicePath, volumeID) {
fmt.Printf("Found and verified VMware device: %s\n", devicePath)
return devicePath, nil
}
}
}
}
return "", fmt.Errorf("device not found for volume %s", volumeID)
}

func (m *mounter) verifyVMwareDevice(devicePath string, volumeID string) bool {
size, err := m.GetBlockSizeBytes(devicePath)
if err != nil {
fmt.Printf("Failed to get device size: %v\n", err)
return false
}
fmt.Printf("Device size: %d bytes\n", size)

mounted, err := m.isDeviceMounted(devicePath)
if err != nil {
fmt.Printf("Failed to check if device is mounted: %v\n", err)
return false
}
if mounted {
fmt.Printf("Device is already mounted: %s\n", devicePath)
return false
}

props, err := m.getDeviceProperties(devicePath)
if err != nil {
fmt.Printf("Failed to get device properties: %v\n", err)
return false
}
fmt.Printf("Device properties: %v\n", props)

return true
}

func (m *mounter) isDeviceMounted(devicePath string) (bool, error) {
output, err := m.Exec.Command("grep", devicePath, "/proc/mounts").Output()
if err != nil {
if strings.Contains(err.Error(), "exit status 1") {
return false, nil
}
return false, err
}
return len(output) > 0, nil
}

func (m *mounter) isDeviceInUse(devicePath string) (bool, error) {
output, err := m.Exec.Command("lsof", devicePath).Output()
if err != nil {
if strings.Contains(err.Error(), "exit status 1") {
return false, nil
}
return false, err
}
return len(output) > 0, nil
}

func (m *mounter) getDeviceProperties(devicePath string) (map[string]string, error) {
output, err := m.Exec.Command("udevadm", "info", "--query=property", devicePath).Output()
if err != nil {
return nil, err
}

props := make(map[string]string)
for _, line := range strings.Split(string(output), "\n") {
if line == "" {
continue
}
parts := strings.Split(line, "=")
if len(parts) == 2 {
props[parts[0]] = parts[1]
}
}

return props, nil
}

func (m *mounter) probeVolume(ctx context.Context) {
logger := klog.FromContext(ctx)
logger.V(2).Info("Scanning SCSI host")
Expand Down
Loading