func (d *LonghornDevice) createDev() error {
	if _, err := os.Stat(DevPath); os.IsNotExist(err) {
		if err := os.MkdirAll(DevPath, 0755); err != nil {
			logrus.Fatalf("device %v: Cannot create directory %v", d.name, DevPath)
		}
	}
	dev := d.getDev()
	if _, err := os.Stat(dev); err == nil {
		logrus.Warnf("Device %s already exists, clean it up", dev)
		if err := util.RemoveDevice(dev); err != nil {
			return errors.Wrapf(err, "cannot cleanup block device file %v", dev)
		}
	}
	if err := util.DuplicateDevice(d.scsiDevice.KernelDevice, dev); err != nil {
		return err
	}
	logrus.Debugf("device %v: Device %s is ready", d.name, dev)
	return nil
}
call with lock hold
package longhorndev
import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"time"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/longhorn/go-iscsi-helper/iscsidev"
	"github.com/longhorn/go-iscsi-helper/types"
	"github.com/longhorn/go-iscsi-helper/util"
)
const (
	SocketDirectory = "/var/run"
	DevPath         = "/dev/longhorn/"
	WaitInterval = time.Second
	WaitCount    = 30
	SwitchWaitInterval = time.Second
	SwitchWaitCount    = 15
)
type LonghornDevice struct {
	*sync.RWMutex
	name     string  
	size     int64
	frontend string
	endpoint string
	scsiDevice *iscsidev.Device
}
type DeviceService interface {
	GetFrontend() string
	SetFrontend(frontend string) error
	UnsetFrontendCheck() error
	UnsetFrontend()
	GetEndpoint() string
	Enabled() bool
	InitDevice() error
	Start() error
	Shutdown() error
	PrepareUpgrade() error
	FinishUpgrade() error
	Expand(size int64) error
}
type DeviceCreator interface {
	NewDevice(name string, size int64, frontend string) (DeviceService, error)
}
type LonghornDeviceCreator struct{}
func (ldc *LonghornDeviceCreator) NewDevice(name string, size int64, frontend string) (DeviceService, error) {
	if name == "" || size == 0 {
		return nil, fmt.Errorf("invalid parameter for creating Longhorn device")
	}
	dev := &LonghornDevice{
		RWMutex: &sync.RWMutex{},
		name:    name,
		size:    size,
	}
	if err := dev.SetFrontend(frontend); err != nil {
		return nil, err
	}
	return dev, nil
}
func (d *LonghornDevice) InitDevice() error {
	d.Lock()
	defer d.Unlock()
	if d.scsiDevice != nil {
		return nil
	}
	if err := d.initScsiDevice(); err != nil {
		return err
	}
	return d.shutdownFrontend()
}
func (d *LonghornDevice) initScsiDevice() error {
	bsOpts := fmt.Sprintf("size=%v", d.size)
	scsiDev, err := iscsidev.NewDevice(d.name, d.GetSocketPath(), "longhorn", bsOpts)
	if err != nil {
		return err
	}
	d.scsiDevice = scsiDev
	return nil
}
func (d *LonghornDevice) Start() error {
	stopCh := make(chan struct{})
	if err := <-d.WaitForSocket(stopCh); err != nil {
		return err
	}
	return d.startScsiDevice(true)
}
func (d *LonghornDevice) startScsiDevice(startScsiDevice bool) (err error) {
	d.Lock()
	defer d.Unlock()
	switch d.frontend {
	case types.FrontendTGTBlockDev:
		if startScsiDevice {
			if d.scsiDevice == nil {
				return fmt.Errorf("There is no iscsi device during the frontend %v starts", d.frontend)
			}
			if err := d.scsiDevice.CreateTarget(); err != nil {
				return err
			}
			if err := d.scsiDevice.StartInitator(); err != nil {
				return err
			}
			if err := d.createDev(); err != nil {
				return err
			}
			logrus.Infof("device %v: SCSI device %s created", d.name, d.scsiDevice.KernelDevice.Name)
		}
		d.endpoint = d.getDev()
		break
	case types.FrontendTGTISCSI:
		if startScsiDevice {
			if d.scsiDevice == nil {
				return fmt.Errorf("There is no iscsi device during the frontend %v starts", d.frontend)
			}
			if err := d.scsiDevice.CreateTarget(); err != nil {
				return err
			}
			logrus.Infof("device %v: iSCSI target %s created", d.name, d.scsiDevice.Target)
		}
		d.endpoint = d.scsiDevice.Target
		break
	default:
		return fmt.Errorf("unknown frontend %v", d.frontend)
	}
	logrus.Debugf("device %v: frontend start succeed", d.name)
	return nil
}
func (d *LonghornDevice) Shutdown() error {
	d.Lock()
	defer d.Unlock()
	if d.scsiDevice == nil {
		return nil
	}
	if err := d.shutdownFrontend(); err != nil {
		return err
	}
	d.scsiDevice = nil
	d.endpoint = ""
	return nil
}
func (d *LonghornDevice) shutdownFrontend() error {
	switch d.frontend {
	case types.FrontendTGTBlockDev:
		dev := d.getDev()
		if err := util.RemoveDevice(dev); err != nil {
			return fmt.Errorf("device %v: fail to remove device %s: %v", d.name, dev, err)
		}
		if err := d.scsiDevice.StopInitiator(); err != nil {
			return fmt.Errorf("device %v: fail to stop SCSI device: %v", d.name, err)
		}
		if err := d.scsiDevice.DeleteTarget(); err != nil {
			return fmt.Errorf("device %v: fail to delete target %v", d.name, d.scsiDevice.Target)
		}
		logrus.Infof("device %v: SCSI device %v shutdown", d.name, dev)
		break
	case types.FrontendTGTISCSI:
		if err := d.scsiDevice.DeleteTarget(); err != nil {
			return fmt.Errorf("device %v: fail to delete target %v", d.name, d.scsiDevice.Target)
		}
		logrus.Infof("device %v: SCSI target %v ", d.name, d.scsiDevice.Target)
		break
	case "":
		logrus.Infof("device %v: skip shutdown frontend since it's not enabled", d.name)
		break
	default:
		return fmt.Errorf("device %v: unknown frontend %v", d.name, d.frontend)
	}
	return nil
}
func (d *LonghornDevice) WaitForSocket(stopCh chan struct{}) chan error {
	errCh := make(chan error)
	go func(errCh chan error, stopCh chan struct{}) {
		socket := d.GetSocketPath()
		timeout := time.After(time.Duration(WaitCount) * WaitInterval)
		ticker := time.NewTicker(WaitInterval)
		defer ticker.Stop()
		tick := ticker.C
		for {
			select {
			case <-timeout:
				errCh <- fmt.Errorf("device %v: wait for socket %v timed out", d.name, socket)
			case <-tick:
				if _, err := os.Stat(socket); err == nil {
					errCh <- nil
					return
				}
				logrus.Infof("device %v: waiting for socket %v to show up", d.name, socket)
			case <-stopCh:
				logrus.Infof("device %v: stop wait for socket routine", d.name)
				return
			}
		}
	}(errCh, stopCh)
	return errCh
}
func (d *LonghornDevice) GetSocketPath() string {
	return filepath.Join(SocketDirectory, "longhorn-"+d.name+".sock")
}
func (d *LonghornDevice) getDev() string {
	return filepath.Join(DevPath, d.name)
}