driver.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. package main
  2. import (
  3. "crypto/md5"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. "sync"
  11. "time"
  12. // Vendors
  13. "iscsi"
  14. "util"
  15. "github.com/Sirupsen/logrus"
  16. "github.com/docker/go-plugins-helpers/volume"
  17. )
  18. var iSCSI iscsi.ISCSIPlugin
  19. type iscsiVolume struct {
  20. Host string
  21. TargetIQN string
  22. Options []string
  23. Mountpoint string
  24. connections int
  25. }
  26. type iscsiDriver struct {
  27. sync.RWMutex
  28. root string
  29. statePath string
  30. volumes map[string]*iscsiVolume
  31. }
  32. // Constructor
  33. func newiscsiDriver(root string) (*iscsiDriver, error) {
  34. logrus.WithField("method", "new driver").Debug(root)
  35. fi, err := os.Lstat(root)
  36. if os.IsNotExist(err) {
  37. if err := os.MkdirAll(root, 0755); err != nil {
  38. return nil, err
  39. }
  40. } else if err != nil {
  41. return nil, err
  42. }
  43. if fi != nil && !fi.IsDir() {
  44. return nil, util.LogError("%v already exist and it's not a directory", root)
  45. }
  46. stateFolder := filepath.Join(root, "plugins", "docker-iscsi")
  47. fi, err = os.Lstat(stateFolder)
  48. if os.IsNotExist(err) {
  49. if err := os.MkdirAll(stateFolder, 0755); err != nil {
  50. return nil, err
  51. }
  52. } else if err != nil {
  53. return nil, err
  54. }
  55. if fi != nil && !fi.IsDir() {
  56. return nil, util.LogError("%v already exist and it's not a directory", stateFolder)
  57. }
  58. d := &iscsiDriver{
  59. root: filepath.Join(root, "volumes"),
  60. statePath: filepath.Join(stateFolder, "iscsi-state.json"),
  61. volumes: map[string]*iscsiVolume{},
  62. }
  63. data, err := ioutil.ReadFile(d.statePath)
  64. if err != nil {
  65. if os.IsNotExist(err) {
  66. logrus.WithField("statePath", d.statePath).Debug("no state found")
  67. } else {
  68. return nil, err
  69. }
  70. } else {
  71. if err := json.Unmarshal(data, &d.volumes); err != nil {
  72. return nil, err
  73. }
  74. }
  75. return d, nil
  76. }
  77. // Docker API :- Create
  78. func (d *iscsiDriver) Create(r *volume.CreateRequest) error {
  79. logrus.WithField("method", "create").Debugf("%#v", r)
  80. d.Lock()
  81. defer d.Unlock()
  82. v := &iscsiVolume{}
  83. for key, val := range r.Options {
  84. switch key {
  85. case "targetiqn":
  86. v.TargetIQN = val
  87. case "host":
  88. v.Host = val
  89. default:
  90. if val != "" {
  91. v.Options = append(v.Options, key+"="+val)
  92. } else {
  93. v.Options = append(v.Options, key)
  94. }
  95. }
  96. }
  97. if v.Host == "" {
  98. return util.LogError("'host' option required")
  99. }
  100. if v.TargetIQN == "" {
  101. return util.LogError("'targetiqn' option required")
  102. }
  103. v.Mountpoint = filepath.Join(d.root, fmt.Sprintf("%x", md5.Sum([]byte(v.TargetIQN))))
  104. d.volumes[r.Name] = v
  105. d.saveState()
  106. return nil
  107. }
  108. // Docker API :- Remove
  109. func (d *iscsiDriver) Remove(r *volume.RemoveRequest) error {
  110. logrus.WithField("method", "remove").Debugf("%#v", r)
  111. d.Lock()
  112. defer d.Unlock()
  113. v, ok := d.volumes[r.Name]
  114. if !ok {
  115. return util.LogError("volume %s not found", r.Name)
  116. }
  117. if v.connections != 0 {
  118. return util.LogError("volume %s is currently used by a container", r.Name)
  119. }
  120. if err := os.RemoveAll(v.Mountpoint); err != nil {
  121. return util.LogError(err.Error())
  122. }
  123. delete(d.volumes, r.Name)
  124. d.saveState()
  125. return nil
  126. }
  127. // Docker API :- Mount
  128. func (d *iscsiDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) {
  129. logrus.WithField("method", "mount").Debugf("%#v", r)
  130. d.Lock()
  131. defer d.Unlock()
  132. v, ok := d.volumes[r.Name]
  133. if !ok {
  134. return &volume.MountResponse{}, util.LogError("volume %s not found", r.Name)
  135. }
  136. if v.connections == 0 {
  137. fi, err := os.Lstat(v.Mountpoint)
  138. if os.IsNotExist(err) {
  139. if err := os.MkdirAll(v.Mountpoint, 0755); err != nil {
  140. return &volume.MountResponse{}, util.LogError(err.Error())
  141. }
  142. } else if err != nil {
  143. return &volume.MountResponse{}, util.LogError(err.Error())
  144. }
  145. if fi != nil && !fi.IsDir() {
  146. return &volume.MountResponse{}, util.LogError("%v already exist and it's not a directory", v.Mountpoint)
  147. }
  148. if err := d.mountVolume(v); err != nil {
  149. return &volume.MountResponse{}, util.LogError(err.Error())
  150. }
  151. }
  152. v.connections++
  153. return &volume.MountResponse{Mountpoint: v.Mountpoint}, nil
  154. }
  155. // Docker API :- Path
  156. func (d *iscsiDriver) Path(r *volume.PathRequest) (*volume.PathResponse, error) {
  157. logrus.WithField("method", "path").Debugf("%#v", r)
  158. d.RLock()
  159. defer d.RUnlock()
  160. v, ok := d.volumes[r.Name]
  161. if !ok {
  162. return &volume.PathResponse{}, util.LogError("volume %s not found", r.Name)
  163. }
  164. return &volume.PathResponse{Mountpoint: v.Mountpoint}, nil
  165. }
  166. // Docker API :- Unmount
  167. func (d *iscsiDriver) Unmount(r *volume.UnmountRequest) error {
  168. logrus.WithField("method", "unmount").Debugf("%#v", r)
  169. d.Lock()
  170. defer d.Unlock()
  171. v, ok := d.volumes[r.Name]
  172. if !ok {
  173. return util.LogError("volume %s not found", r.Name)
  174. }
  175. v.connections--
  176. if v.connections <= 0 {
  177. if err := d.unmountVolume(v); err != nil {
  178. return util.LogError(err.Error())
  179. }
  180. v.connections = 0
  181. }
  182. return nil
  183. }
  184. // Docker API :- Get
  185. func (d *iscsiDriver) Get(r *volume.GetRequest) (*volume.GetResponse, error) {
  186. logrus.WithField("method", "get").Debugf("%#v", r)
  187. d.Lock()
  188. defer d.Unlock()
  189. v, ok := d.volumes[r.Name]
  190. if !ok {
  191. return &volume.GetResponse{}, util.LogError("volume %s not found", r.Name)
  192. }
  193. return &volume.GetResponse{Volume: &volume.Volume{Name: r.Name, Mountpoint: v.Mountpoint}}, nil
  194. }
  195. // Docker API :- List
  196. func (d *iscsiDriver) List() (*volume.ListResponse, error) {
  197. logrus.WithField("method", "list").Debugf("")
  198. d.Lock()
  199. defer d.Unlock()
  200. var vols []*volume.Volume
  201. for name, v := range d.volumes {
  202. vols = append(vols, &volume.Volume{Name: name, Mountpoint: v.Mountpoint})
  203. }
  204. return &volume.ListResponse{Volumes: vols}, nil
  205. }
  206. // Docker API :- Capabilities
  207. func (d *iscsiDriver) Capabilities() *volume.CapabilitiesResponse {
  208. logrus.WithField("method", "capabilities").Debugf("")
  209. return &volume.CapabilitiesResponse{Capabilities: volume.Capability{Scope: "local"}}
  210. }
  211. // Local Functions
  212. func (d *iscsiDriver) mountVolume(v *iscsiVolume) error {
  213. // Discover iSCSI LUNs
  214. err := iSCSI.DiscoverLUNs(v.Host)
  215. if err != nil {
  216. return err
  217. }
  218. if !iSCSI.TargetLoggedIn(v.TargetIQN) {
  219. // Login to iSCSI Target
  220. err = iSCSI.LoginTarget(v.TargetIQN, v.Host)
  221. if err != nil {
  222. return err
  223. }
  224. // Wait for Physical Volume to appear
  225. time.Sleep(500 * time.Millisecond)
  226. }
  227. out := ""
  228. errMsg := ""
  229. // TODO: Move this feature to iscsi driver
  230. // Get target device
  231. cmd := "iscsiadm -m session | grep \"" + v.TargetIQN + "\" | head -n 1 | sed -e 's/.*\\[\\(.*\\)\\].*/\\1/g'"
  232. out, errMsg = util.ExecuteCommandString(cmd)
  233. if len(out) > 0 {
  234. logrus.Debug(out)
  235. }
  236. if len(errMsg) > 0 {
  237. err := fmt.Errorf("Unable to find device: %s", errMsg)
  238. return err
  239. }
  240. out = strings.TrimSuffix(out, "\n")
  241. cmd = "iscsiadm -m session -r " + out + " -P 3 | grep \"Attached scsi disk\" | sed -e 's/.*Attached scsi disk \\([^\\t]*\\).*/\\/dev\\/\\1/g'"
  242. out, errMsg = util.ExecuteCommandString(cmd)
  243. if len(out) > 0 {
  244. logrus.Debug(out)
  245. }
  246. if len(errMsg) > 0 {
  247. err := fmt.Errorf("Unable to locate device: %s", errMsg)
  248. return err
  249. }
  250. device := strings.TrimSuffix(out, "\n")
  251. // FSCK
  252. cmd = "e2fsck -p " + device
  253. out, errMsg = util.ExecuteCommandString(cmd)
  254. if len(out) > 0 {
  255. logrus.Debug(out)
  256. }
  257. if len(errMsg) > 0 {
  258. err := fmt.Errorf("Unable to Check Disk: %s", errMsg)
  259. return err
  260. }
  261. // Mount
  262. cmd = "mount -o discard " + device + " " + v.Mountpoint
  263. out, errMsg = util.ExecuteCommandString(cmd)
  264. if len(out) > 0 {
  265. logrus.Debug(out)
  266. }
  267. if len(errMsg) > 0 {
  268. err := fmt.Errorf("Unable to Mount Volume: %s", errMsg)
  269. return err
  270. }
  271. return nil
  272. }
  273. func (d *iscsiDriver) unmountVolume(v *iscsiVolume) error {
  274. // Unmount
  275. cmd := "umount " + v.Mountpoint
  276. out, errMsg := util.ExecuteCommandString(cmd)
  277. if len(out) > 0 {
  278. logrus.Debug(out)
  279. }
  280. if len(errMsg) > 0 {
  281. err := fmt.Errorf("Unable to Unmount Volume: %s", errMsg)
  282. logrus.Info(err)
  283. }
  284. if iSCSI.TargetLoggedIn(v.TargetIQN) {
  285. // Logout from iSCSI Target
  286. err := iSCSI.LogoutTarget(v.TargetIQN, v.Host)
  287. if err != nil {
  288. return err
  289. }
  290. }
  291. return nil
  292. }
  293. func (d *iscsiDriver) saveState() {
  294. data, err := json.Marshal(d.volumes)
  295. if err != nil {
  296. logrus.WithField("statePath", d.statePath).Error(err)
  297. return
  298. }
  299. if err := ioutil.WriteFile(d.statePath, data, 0644); err != nil {
  300. logrus.WithField("savestate", d.statePath).Error(err)
  301. }
  302. }