| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- package main
- import (
- "crypto/md5"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "sync"
- "time"
- // Vendors
- "iscsi"
- "util"
- "github.com/Sirupsen/logrus"
- "github.com/docker/go-plugins-helpers/volume"
- )
- var iSCSI iscsi.ISCSIPlugin
- type iscsiVolume struct {
- Host string
- TargetIQN string
- Options []string
- Mountpoint string
- connections int
- }
- type iscsiDriver struct {
- sync.RWMutex
- root string
- statePath string
- volumes map[string]*iscsiVolume
- }
- // Constructor
- func newiscsiDriver(root string) (*iscsiDriver, error) {
- logrus.WithField("method", "new driver").Debug(root)
- fi, err := os.Lstat(root)
- if os.IsNotExist(err) {
- if err := os.MkdirAll(root, 0755); err != nil {
- return nil, err
- }
- } else if err != nil {
- return nil, err
- }
- if fi != nil && !fi.IsDir() {
- return nil, util.LogError("%v already exist and it's not a directory", root)
- }
- stateFolder := filepath.Join(root, "plugins", "docker-iscsi")
- fi, err = os.Lstat(stateFolder)
- if os.IsNotExist(err) {
- if err := os.MkdirAll(stateFolder, 0755); err != nil {
- return nil, err
- }
- } else if err != nil {
- return nil, err
- }
- if fi != nil && !fi.IsDir() {
- return nil, util.LogError("%v already exist and it's not a directory", stateFolder)
- }
- d := &iscsiDriver{
- root: filepath.Join(root, "volumes"),
- statePath: filepath.Join(stateFolder, "iscsi-state.json"),
- volumes: map[string]*iscsiVolume{},
- }
- data, err := ioutil.ReadFile(d.statePath)
- if err != nil {
- if os.IsNotExist(err) {
- logrus.WithField("statePath", d.statePath).Debug("no state found")
- } else {
- return nil, err
- }
- } else {
- if err := json.Unmarshal(data, &d.volumes); err != nil {
- return nil, err
- }
- }
- return d, nil
- }
- // Docker API :- Create
- func (d *iscsiDriver) Create(r *volume.CreateRequest) error {
- logrus.WithField("method", "create").Debugf("%#v", r)
- d.Lock()
- defer d.Unlock()
- v := &iscsiVolume{}
- for key, val := range r.Options {
- switch key {
- case "targetiqn":
- v.TargetIQN = val
- case "host":
- v.Host = val
- default:
- if val != "" {
- v.Options = append(v.Options, key+"="+val)
- } else {
- v.Options = append(v.Options, key)
- }
- }
- }
- if v.Host == "" {
- return util.LogError("'host' option required")
- }
- if v.TargetIQN == "" {
- return util.LogError("'targetiqn' option required")
- }
- v.Mountpoint = filepath.Join(d.root, fmt.Sprintf("%x", md5.Sum([]byte(v.TargetIQN))))
- d.volumes[r.Name] = v
- d.saveState()
- return nil
- }
- // Docker API :- Remove
- func (d *iscsiDriver) Remove(r *volume.RemoveRequest) error {
- logrus.WithField("method", "remove").Debugf("%#v", r)
- d.Lock()
- defer d.Unlock()
- v, ok := d.volumes[r.Name]
- if !ok {
- return util.LogError("volume %s not found", r.Name)
- }
- if v.connections != 0 {
- return util.LogError("volume %s is currently used by a container", r.Name)
- }
- if err := os.RemoveAll(v.Mountpoint); err != nil {
- return util.LogError(err.Error())
- }
- delete(d.volumes, r.Name)
- d.saveState()
- return nil
- }
- // Docker API :- Mount
- func (d *iscsiDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) {
- logrus.WithField("method", "mount").Debugf("%#v", r)
- d.Lock()
- defer d.Unlock()
- v, ok := d.volumes[r.Name]
- if !ok {
- return &volume.MountResponse{}, util.LogError("volume %s not found", r.Name)
- }
- if v.connections == 0 {
- fi, err := os.Lstat(v.Mountpoint)
- if os.IsNotExist(err) {
- if err := os.MkdirAll(v.Mountpoint, 0755); err != nil {
- return &volume.MountResponse{}, util.LogError(err.Error())
- }
- } else if err != nil {
- return &volume.MountResponse{}, util.LogError(err.Error())
- }
- if fi != nil && !fi.IsDir() {
- return &volume.MountResponse{}, util.LogError("%v already exist and it's not a directory", v.Mountpoint)
- }
- if err := d.mountVolume(v); err != nil {
- return &volume.MountResponse{}, util.LogError(err.Error())
- }
- }
- v.connections++
- return &volume.MountResponse{Mountpoint: v.Mountpoint}, nil
- }
- // Docker API :- Path
- func (d *iscsiDriver) Path(r *volume.PathRequest) (*volume.PathResponse, error) {
- logrus.WithField("method", "path").Debugf("%#v", r)
- d.RLock()
- defer d.RUnlock()
- v, ok := d.volumes[r.Name]
- if !ok {
- return &volume.PathResponse{}, util.LogError("volume %s not found", r.Name)
- }
- return &volume.PathResponse{Mountpoint: v.Mountpoint}, nil
- }
- // Docker API :- Unmount
- func (d *iscsiDriver) Unmount(r *volume.UnmountRequest) error {
- logrus.WithField("method", "unmount").Debugf("%#v", r)
- d.Lock()
- defer d.Unlock()
- v, ok := d.volumes[r.Name]
- if !ok {
- return util.LogError("volume %s not found", r.Name)
- }
- v.connections--
- if v.connections <= 0 {
- if err := d.unmountVolume(v); err != nil {
- return util.LogError(err.Error())
- }
- v.connections = 0
- }
- return nil
- }
- // Docker API :- Get
- func (d *iscsiDriver) Get(r *volume.GetRequest) (*volume.GetResponse, error) {
- logrus.WithField("method", "get").Debugf("%#v", r)
- d.Lock()
- defer d.Unlock()
- v, ok := d.volumes[r.Name]
- if !ok {
- return &volume.GetResponse{}, util.LogError("volume %s not found", r.Name)
- }
- return &volume.GetResponse{Volume: &volume.Volume{Name: r.Name, Mountpoint: v.Mountpoint}}, nil
- }
- // Docker API :- List
- func (d *iscsiDriver) List() (*volume.ListResponse, error) {
- logrus.WithField("method", "list").Debugf("")
- d.Lock()
- defer d.Unlock()
- var vols []*volume.Volume
- for name, v := range d.volumes {
- vols = append(vols, &volume.Volume{Name: name, Mountpoint: v.Mountpoint})
- }
- return &volume.ListResponse{Volumes: vols}, nil
- }
- // Docker API :- Capabilities
- func (d *iscsiDriver) Capabilities() *volume.CapabilitiesResponse {
- logrus.WithField("method", "capabilities").Debugf("")
- return &volume.CapabilitiesResponse{Capabilities: volume.Capability{Scope: "local"}}
- }
- // Local Functions
- func (d *iscsiDriver) mountVolume(v *iscsiVolume) error {
- // Discover iSCSI LUNs
- err := iSCSI.DiscoverLUNs(v.Host)
- if err != nil {
- return err
- }
- // Login to iSCSI Target
- err = iSCSI.LoginTarget(v.TargetIQN, v.Host)
- if err != nil {
- return err
- }
- // Wait for Physical Volume to appear
- time.Sleep(500 * time.Millisecond)
- out := ""
- errMsg := ""
- diskPathFolder := "/dev/disk/by-path"
- fi, err := os.Lstat(diskPathFolder)
- if os.IsNotExist(err) {
- // Disk By Path doesn't exist
- iscsiPathFolder := "/host/tmp/iscsi"
- fi, err = os.Lstat(iscsiPathFolder)
- if os.IsNotExist(err) {
- // iSCSI Administration folder doesn't exist
- return err
- } else {
- // iSCSI Administration folder exists, seen on Synology systems
- // Get target device
- cmd := "ls /host/tmp/iscsi | grep \"" + v.TargetIQN + "\" | head -n 1"
- out, errMsg = util.ExecuteCommandString(cmd)
- if len(out) > 0 {
- logrus.Debug(out)
- }
- if len(errMsg) > 0 {
- err := fmt.Errorf("Unable to find device: %s", errMsg)
- return err
- }
- cmd = "readlink -nf /host/tmp/iscsi/" + out
- out, errMsg = util.ExecuteCommandString(cmd)
- if len(out) > 0 {
- logrus.Debug(out)
- }
- if len(errMsg) > 0 {
- err := fmt.Errorf("Unable to locate device: %s", errMsg)
- return err
- }
- }
- } else {
- // Disk By Path exists
- // Get target device
- cmd := "ls /dev/disk/by-path | grep \"" + v.TargetIQN + "\" | head -n 1"
- out, errMsg = util.ExecuteCommandString(cmd)
- if len(out) > 0 {
- logrus.Debug(out)
- }
- if len(errMsg) > 0 {
- err := fmt.Errorf("Unable to find device: %s", errMsg)
- return err
- }
- cmd = "readlink -nf /dev/disk/by-path/" + out
- out, errMsg = util.ExecuteCommandString(cmd)
- if len(out) > 0 {
- logrus.Debug(out)
- }
- if len(errMsg) > 0 {
- err := fmt.Errorf("Unable to locate device: %s", errMsg)
- return err
- }
- }
- _ = fi
- // Mount
- cmd := "mount " + out + " " + v.Mountpoint
- out, errMsg = util.ExecuteCommandString(cmd)
- if len(out) > 0 {
- logrus.Debug(out)
- }
- if len(errMsg) > 0 {
- err := fmt.Errorf("Unable to Mount Volume: %s", errMsg)
- return err
- }
- return nil
- }
- func (d *iscsiDriver) unmountVolume(v *iscsiVolume) error {
- // Unmount
- cmd := "umount " + v.Mountpoint
- out, errMsg := util.ExecuteCommandString(cmd)
- if len(out) > 0 {
- logrus.Debug(out)
- }
- if len(errMsg) > 0 {
- err := fmt.Errorf("Unable to Unmount Volume: %s", errMsg)
- return err
- }
- // Logout from iSCSI Target
- err := iSCSI.LogoutTarget(v.TargetIQN, v.Host)
- if err != nil {
- return err
- }
- return nil
- }
- func (d *iscsiDriver) saveState() {
- data, err := json.Marshal(d.volumes)
- if err != nil {
- logrus.WithField("statePath", d.statePath).Error(err)
- return
- }
- if err := ioutil.WriteFile(d.statePath, data, 0644); err != nil {
- logrus.WithField("savestate", d.statePath).Error(err)
- }
- }
|