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  }