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 }