github.com/yasker/longhorn-engine@v0.0.0-20160621014712-6ed6cfca0729/frontend/tcmu/frontend.go (about) 1 package tcmu 2 3 import ( 4 "crypto/md5" 5 "encoding/hex" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/Sirupsen/logrus" 17 "github.com/rancher/longhorn/types" 18 ) 19 20 const ( 21 devPath = "/dev/longhorn/" 22 configDir = "/sys/kernel/config/target/core/user_42" 23 scsiDir = "/sys/kernel/config/target/loopback" 24 wwnPrefix = "naa.6001405" 25 teardownRetryWait = 1 26 teardownAttempts = 2 27 ) 28 29 func New() types.Frontend { 30 return &Tcmu{} 31 } 32 33 type Tcmu struct { 34 volume string 35 } 36 37 func (t *Tcmu) Activate(name string, size, sectorSize int64, rw types.ReaderWriterAt) error { 38 t.volume = name 39 40 t.Shutdown() 41 42 if err := PreEnableTcmu(name, size, sectorSize); err != nil { 43 return err 44 } 45 46 if err := start(name, rw); err != nil { 47 return err 48 } 49 50 return PostEnableTcmu(name) 51 } 52 53 func (t *Tcmu) Shutdown() error { 54 return TeardownTcmu(t.volume) 55 } 56 57 func PreEnableTcmu(volume string, size, sectorSize int64) error { 58 err := writeLines(path.Join(configDir, volume, "control"), []string{ 59 fmt.Sprintf("dev_size=%d", size), 60 fmt.Sprintf("dev_config=%s", GetDevConfig(volume)), 61 fmt.Sprintf("hw_block_size=%d", sectorSize), 62 "async=1", 63 }) 64 if err != nil { 65 return err 66 } 67 68 return writeLines(path.Join(configDir, volume, "enable"), []string{ 69 "1", 70 }) 71 } 72 73 func PostEnableTcmu(volume string) error { 74 prefix, nexusWnn := getScsiPrefixAndWnn(volume) 75 76 err := writeLines(path.Join(prefix, "nexus"), []string{ 77 nexusWnn, 78 }) 79 if err != nil { 80 return err 81 } 82 83 lunPath := getLunPath(prefix) 84 logrus.Infof("Creating directory: %s", lunPath) 85 if err := os.MkdirAll(lunPath, 0755); err != nil && !os.IsExist(err) { 86 return err 87 } 88 89 logrus.Infof("Linking: %s => %s", path.Join(lunPath, volume), path.Join(configDir, volume)) 90 if err := os.Symlink(path.Join(configDir, volume), path.Join(lunPath, volume)); err != nil { 91 return err 92 } 93 94 return createDevice(volume) 95 } 96 97 func createDevice(volume string) error { 98 os.MkdirAll(devPath, 0700) 99 100 dev := devPath + volume 101 102 if _, err := os.Stat(dev); err == nil { 103 return fmt.Errorf("Device %s already exists, can not create", dev) 104 } 105 106 tgt, _ := getScsiPrefixAndWnn(volume) 107 108 address, err := ioutil.ReadFile(path.Join(tgt, "address")) 109 if err != nil { 110 return err 111 } 112 113 found := false 114 matches := []string{} 115 path := fmt.Sprintf("/sys/bus/scsi/devices/%s*/block/*/dev", strings.TrimSpace(string(address))) 116 for i := 0; i < 30; i++ { 117 var err error 118 matches, err = filepath.Glob(path) 119 if len(matches) > 0 && err == nil { 120 found = true 121 break 122 } 123 124 logrus.Infof("Waiting for %s", path) 125 time.Sleep(1 * time.Second) 126 } 127 128 if !found { 129 return fmt.Errorf("Failed to find %s", path) 130 } 131 132 if len(matches) == 0 { 133 return fmt.Errorf("Failed to find %s", path) 134 } 135 136 if len(matches) > 1 { 137 return fmt.Errorf("Too many matches for %s, found %d", path, len(matches)) 138 } 139 140 majorMinor, err := ioutil.ReadFile(matches[0]) 141 if err != nil { 142 return err 143 } 144 145 parts := strings.Split(strings.TrimSpace(string(majorMinor)), ":") 146 if len(parts) != 2 { 147 return fmt.Errorf("Invalid major:minor string %s", string(majorMinor)) 148 } 149 150 major, err := strconv.Atoi(parts[0]) 151 if err != nil { 152 return err 153 } 154 minor, err := strconv.Atoi(parts[1]) 155 if err != nil { 156 return err 157 } 158 159 logrus.Infof("Creating device %s %d:%d", dev, major, minor) 160 return mknod(dev, major, minor) 161 } 162 163 func mknod(device string, major, minor int) error { 164 var fileMode os.FileMode = 0600 165 fileMode |= syscall.S_IFBLK 166 dev := int((major << 8) | (minor & 0xff) | ((minor & 0xfff00) << 12)) 167 168 return syscall.Mknod(device, uint32(fileMode), dev) 169 } 170 171 func GetDevConfig(volume string) string { 172 return fmt.Sprintf("longhorn//%s", volume) 173 } 174 175 func getScsiPrefixAndWnn(volume string) (string, string) { 176 suffix := genSuffix(volume) 177 loopbackWnn := wwnPrefix + "r0" + suffix 178 nexusWnn := wwnPrefix + "r1" + suffix 179 return path.Join(scsiDir, loopbackWnn, "tpgt_1"), nexusWnn 180 } 181 182 func getLunPath(prefix string) string { 183 return path.Join(prefix, "lun", "lun_0") 184 } 185 186 func TeardownTcmu(volume string) error { 187 var err error 188 for i := 1; i <= teardownAttempts; i++ { 189 logrus.Info("Starting TCMU teardown.") 190 if err := teardown(volume); err != nil { 191 if i < teardownAttempts { 192 logrus.Infof("Error occurred during TCMU teardown. Attempting again after %v second sleep. Error: %v", teardownRetryWait, err) 193 time.Sleep(time.Second * teardownRetryWait) 194 continue 195 } else { 196 break 197 } 198 } 199 stop() 200 if err := finishTeardown(volume); err != nil { 201 if i < teardownAttempts { 202 logrus.Infof("Error occurred during TCMU teardown. Attempting again after %v second sleep. Error: %v", teardownRetryWait, err) 203 time.Sleep(time.Second * teardownRetryWait) 204 continue 205 } else { 206 break 207 } 208 209 } 210 logrus.Info("TCMU teardown successful.") 211 break 212 } 213 return err 214 } 215 216 func teardown(volume string) error { 217 dev := devPath + volume 218 tpgtPath, _ := getScsiPrefixAndWnn(volume) 219 lunPath := getLunPath(tpgtPath) 220 221 /* 222 We're removing: 223 /sys/kernel/config/target/loopback/naa.<id>/tpgt_1/lun/lun_0/<volume name> 224 /sys/kernel/config/target/loopback/naa.<id>/tpgt_1/lun/lun_0 225 /sys/kernel/config/target/loopback/naa.<id>/tpgt_1 226 /sys/kernel/config/target/loopback/naa.<id> 227 */ 228 pathsToRemove := []string{ 229 path.Join(lunPath, volume), 230 lunPath, 231 tpgtPath, 232 path.Dir(tpgtPath), 233 } 234 235 for _, p := range pathsToRemove { 236 err := remove(p) 237 if err != nil { 238 return err 239 } 240 } 241 242 // Should be cleaned up automatically, but if it isn't remove it 243 if _, err := os.Stat(dev); err == nil { 244 err := remove(dev) 245 if err != nil { 246 return err 247 } 248 } 249 250 return nil 251 } 252 253 func finishTeardown(volume string) error { 254 /* 255 We're removing: 256 /sys/kernel/config/target/core/user_42/<volume name> 257 */ 258 pathsToRemove := []string{ 259 path.Join(configDir, volume), 260 } 261 262 for _, p := range pathsToRemove { 263 err := remove(p) 264 if err != nil { 265 return err 266 } 267 } 268 269 return nil 270 } 271 func removeAsync(path string, done chan<- error) { 272 logrus.Infof("Removing: %s", path) 273 if err := os.Remove(path); err != nil && !os.IsNotExist(err) { 274 logrus.Errorf("Unable to remove: %v", path) 275 done <- err 276 } 277 logrus.Debugf("Removed: %s", path) 278 done <- nil 279 } 280 281 func remove(path string) error { 282 done := make(chan error) 283 go removeAsync(path, done) 284 select { 285 case err := <-done: 286 return err 287 case <-time.After(30 * time.Second): 288 return fmt.Errorf("Timeout trying to delete %s.", path) 289 } 290 } 291 292 func writeLines(target string, lines []string) error { 293 dir := path.Dir(target) 294 if stat, err := os.Stat(dir); os.IsNotExist(err) { 295 logrus.Infof("Creating directory: %s", dir) 296 if err := os.MkdirAll(dir, 0755); err != nil { 297 return err 298 } 299 } else if !stat.IsDir() { 300 return fmt.Errorf("%s is not a directory", dir) 301 } 302 303 for _, line := range lines { 304 content := []byte(line + "\n") 305 logrus.Infof("Setting %s: %s", target, line) 306 if err := ioutil.WriteFile(target, content, 0755); err != nil { 307 logrus.Errorf("Failed to write %s to %s: %v", line, target, err) 308 return err 309 } 310 } 311 312 return nil 313 } 314 315 func genSuffix(volume string) string { 316 digest := md5.New() 317 digest.Write([]byte(volume)) 318 return hex.EncodeToString(digest.Sum([]byte{}))[:8] 319 }