Files
Docker-Volume-iSCSI/driver.go
T

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)
}
}