372 lines
7.8 KiB
Go
372 lines
7.8 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"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
|
|
}
|
|
|
|
if !iSCSI.TargetLoggedIn(v.TargetIQN) {
|
|
// 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 := ""
|
|
|
|
// TODO: Move this feature to iscsi driver
|
|
|
|
// Get target device
|
|
cmd := "iscsiadm -m session | grep \"" + v.TargetIQN + "\" | head -n 1 | sed -e 's/.*\\[\\(.*\\)\\].*/\\1/g'"
|
|
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
|
|
}
|
|
|
|
out = strings.TrimSuffix(out, "\n")
|
|
|
|
cmd = "iscsiadm -m session -r " + out + " -P 3 | grep \"Attached scsi disk\" | sed -e 's/.*Attached scsi disk \\([^\\t]*\\).*/\\/dev\\/\\1/g'"
|
|
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
|
|
}
|
|
|
|
out = strings.TrimSuffix(out, "\n")
|
|
|
|
// 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)
|
|
logrus.Info(err)
|
|
}
|
|
|
|
if iSCSI.TargetLoggedIn(v.TargetIQN) {
|
|
// 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)
|
|
}
|
|
}
|
|
|