github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/integration_tests/udev/disks.go (about)

     1  /*
     2  Copyright 2019 The OpenEBS Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package udev
    18  
    19  import (
    20  	"fmt"
    21  	"math/rand" // nosec G404
    22  	"os"
    23  	"path/filepath"
    24  	"strconv"
    25  	"syscall"
    26  	"time"
    27  
    28  	"github.com/diskfs/go-diskfs"
    29  	"github.com/diskfs/go-diskfs/disk"
    30  	"github.com/diskfs/go-diskfs/filesystem"
    31  	"github.com/openebs/node-disk-manager/integration_tests/utils"
    32  )
    33  
    34  // Disk and system attributes corresponding to backing image and
    35  // the loop device
    36  const (
    37  	imageDirectory = "/tmp"
    38  	syspath        = "/sys/class/block"
    39  )
    40  
    41  // Disk has the attributes of a virtual disk which is emulated for integration
    42  // testing.
    43  type Disk struct {
    44  	// Size in bytes
    45  	Size int64
    46  	// The backing image name
    47  	// eg: /tmp/fake123
    48  	imageName string
    49  	// the disk name
    50  	// eg: /dev/loop9002
    51  	Name string
    52  	// mount point if any
    53  	MountPoints []string
    54  }
    55  
    56  // NewDisk creates a Disk struct, with a specified size. Also the
    57  // random disk image name is generated. The actual image is generated only when
    58  // we try to attach the disk
    59  func NewDisk(size int64) Disk {
    60  	disk := Disk{
    61  		Size:      size,
    62  		imageName: generateDiskImageName(),
    63  		Name:      "",
    64  	}
    65  	return disk
    66  }
    67  
    68  func (disk *Disk) createDiskImage() error {
    69  	// no of blocks
    70  	/*count := disk.Size / blockSize
    71  	createImageCommand := "dd if=/dev/zero of=" + disk.imageName + " bs=" + strconv.Itoa(blockSize) + " count=" + strconv.Itoa(int(count))
    72  	err := utils.RunCommand(createImageCommand)*/
    73  	f, err := os.Create(disk.imageName)
    74  	if err != nil {
    75  		return fmt.Errorf("error creating disk image. Error : %v", err)
    76  	}
    77  	err = f.Truncate(disk.Size)
    78  	if err != nil {
    79  		return fmt.Errorf("error truncating disk image. Error : %v", err)
    80  	}
    81  
    82  	return nil
    83  }
    84  
    85  func (disk *Disk) createLoopDevice() error {
    86  	var err error
    87  	if _, err = os.Stat(disk.imageName); err != nil {
    88  		err = disk.createDiskImage()
    89  		if err != nil {
    90  			return err
    91  		}
    92  	}
    93  
    94  	deviceName := getLoopDevName()
    95  	devicePath := "/dev/" + deviceName
    96  	// create the loop device using losetup
    97  	createLoopDeviceCommand := "losetup " + devicePath + " " + disk.imageName
    98  	err = utils.RunCommandWithSudo(createLoopDeviceCommand)
    99  	if err != nil {
   100  		return fmt.Errorf("error creating loop device. Error : %v", err)
   101  	}
   102  	disk.Name = devicePath
   103  	return nil
   104  }
   105  
   106  // DetachAndDeleteDisk triggers a udev remove event. It detaches the loop device from the backing
   107  // image. Also deletes the backing image and block device file in /dev
   108  func (disk *Disk) DetachAndDeleteDisk() error {
   109  	if disk.Name == "" {
   110  		return fmt.Errorf("no such disk present for deletion")
   111  	}
   112  	detachLoopCommand := "losetup -d " + disk.Name
   113  	err := utils.RunCommandWithSudo(detachLoopCommand)
   114  	if err != nil {
   115  		return fmt.Errorf("cannot detach loop device. Error : %v", err)
   116  	}
   117  	err = TriggerEvent(UdevEventRemove, syspath, disk.Name)
   118  	if err != nil {
   119  		return fmt.Errorf("could not trigger device remove event. Error : %v", err)
   120  	}
   121  	deleteBackingImageCommand := "rm " + disk.imageName
   122  	err = utils.RunCommandWithSudo(deleteBackingImageCommand)
   123  	if err != nil {
   124  		return fmt.Errorf("could not delete backing disk image. Error : %v", err)
   125  	}
   126  	deleteLoopDeviceCommand := "rm " + disk.Name
   127  	err = utils.RunCommandWithSudo(deleteLoopDeviceCommand)
   128  	if err != nil {
   129  		return fmt.Errorf("could not delete loop device. Error : %v", err)
   130  	}
   131  	return nil
   132  }
   133  
   134  // Generates a random image name for the backing file.
   135  // the file name will be of the format fakeXXX, where X=[0-9]
   136  func generateDiskImageName() string {
   137  	rand.Seed(time.Now().UTC().UnixNano())
   138  	randomNumber := 100 + rand.Intn(899)
   139  	imageName := "fake" + strconv.Itoa(randomNumber)
   140  	return imageDirectory + "/" + imageName
   141  }
   142  
   143  // Generates a random loop device name. The name will be of the
   144  // format loop9XXX, where X=[0-9]. The 4 digit numbering is chosen so that
   145  // we get enough disks to be randomly generated and also it does not clash
   146  // with the existing loop devices present in some systems.
   147  func getLoopDevName() string {
   148  	rand.Seed(time.Now().UTC().UnixNano())
   149  	randomNumber := 9000 + rand.Intn(999)
   150  	diskName := "loop" + strconv.Itoa(randomNumber)
   151  	return diskName
   152  }
   153  
   154  // AttachDisk triggers a udev add event for the disk. If the disk is not present, the loop
   155  // device is created and event is triggered
   156  func (disk *Disk) AttachDisk() error {
   157  	if disk.Name == "" {
   158  		if err := disk.createLoopDevice(); err != nil {
   159  			return err
   160  		}
   161  	}
   162  	return TriggerEvent(UdevEventAdd, syspath, disk.Name)
   163  }
   164  
   165  // DetachDisk triggers a udev remove event for the disk
   166  func (disk *Disk) DetachDisk() error {
   167  	return TriggerEvent(UdevEventRemove, syspath, disk.Name)
   168  }
   169  
   170  func (di *Disk) CreateFileSystem() error {
   171  	var err error
   172  	if _, err = os.Stat(di.imageName); err != nil {
   173  		err = di.createDiskImage()
   174  		if err != nil {
   175  			return err
   176  		}
   177  	}
   178  
   179  	d, err := diskfs.Open(di.imageName)
   180  	defer d.File.Close()
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	_, err = d.CreateFilesystem(disk.FilesystemSpec{
   186  		Partition: 0,
   187  		FSType:    filesystem.TypeFat32,
   188  	})
   189  
   190  	if err != nil {
   191  		return fmt.Errorf("failed to create file system: %v", err)
   192  	}
   193  	return nil
   194  }
   195  
   196  func (disk *Disk) Mount(path string) error {
   197  	path = filepath.Clean(path)
   198  	err := syscall.Mount(disk.Name, path, "vfat", 0, "")
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	disk.MountPoints = append(disk.MountPoints, path)
   204  	return nil
   205  }
   206  
   207  func (disk *Disk) Unmount() error {
   208  	var lastErr error = nil
   209  	for _, mp := range disk.MountPoints {
   210  		err := syscall.Unmount(mp, 0)
   211  		if err != nil {
   212  			lastErr = err
   213  		}
   214  	}
   215  	return lastErr
   216  }
   217  
   218  func (disk *Disk) Resize(size int64) error {
   219  	units := [4]string{"", "K", "M", "G"}
   220  	currUnit := -1
   221  	var maxExp int64 = 1
   222  	for maxExp <= size {
   223  		maxExp *= 1024
   224  		currUnit++
   225  	}
   226  	maxExp /= 1024
   227  	size /= maxExp
   228  	err := utils.RunCommand(fmt.Sprintf("dd if=/dev/zero of=%s bs=1%s count=%d",
   229  		disk.imageName, units[currUnit], size))
   230  	if err != nil {
   231  		return err
   232  	}
   233  	return utils.RunCommandWithSudo("losetup -c " + disk.Name)
   234  }