diff --git a/.travis.yml b/.travis.yml index 612fc81..1c1ef20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - 1.7.0 + - 1.10.1 - tip script: - go test ./... diff --git a/README.md b/README.md index 6d0e7f3..53b4fe2 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,38 @@ The method below will install the sysvinit and /etc/default options that can be ``` $ sudo docker-volume-netshare nfs ``` +**2. Run the plugin - adding the correct DOCKER_API_VERSION** +If you are not using the latest stable version of docker engine please specify the version with flag. +For example: +To check docker API version: +``` +docker version +Client: +Version: 17.12.0-ce +API version: 1.35 +Go version: go1.9.2 +Git commit: c97c6d6 +Built: Wed Dec 27 20:11:19 2017 +OS/Arch: linux/amd64 + +Server: +Engine: + Version: 17.12.0-ce + API version: 1.35 (minimum version 1.12) + Go version: go1.9.2 + Git commit: c97c6d6 + Built: Wed Dec 27 20:09:53 2017 + OS/Arch: linux/amd64 + Experimental: false +``` +Here the Docker API Version is 1.35. So you should start the plugin with the right version of Docker API. + +Minimum supported version for the plugin is 1.12. + +``` + $ sudo docker-volume-netshare nfs -a 1.35 +``` + **2. Launch a container** @@ -90,7 +122,7 @@ The method below will install the sysvinit and /etc/default options that can be **1. Run the plugin - can be added to systemd or run in the background** ``` - $ sudo docker-volume-netshare cifs --username user --password pass --domain domain --security security + $ sudo docker-volume-netshare cifs --username user --password pass --domain domain --security security -a docker_api_version ``` **2. Launch a container** @@ -122,7 +154,7 @@ See example: **2. Run the plugin** ``` - $ sudo docker-volume-netshare cifs + $ sudo docker-volume-netshare cifs -a docker_api_version ``` **3. Launch a container** @@ -140,7 +172,7 @@ options and other info can be eliminated when running a container. **1. Run the plugin - can be added to systemd or run in the background** ``` - $ sudo docker-volume-netshare cifs + $ sudo docker-volume-netshare cifs -a docker_api_version ``` **2. Create a Volume** diff --git a/netshare/drivers/ceph.go b/netshare/drivers/ceph.go index c262769..1af223b 100644 --- a/netshare/drivers/ceph.go +++ b/netshare/drivers/ceph.go @@ -24,9 +24,9 @@ type cephDriver struct { cephopts map[string]string } -func NewCephDriver(root string, username string, password string, context string, cephmount string, cephport string, localmount string, cephopts string) cephDriver { +func NewCephDriver(root string, username string, password string, context string, cephmount string, cephport string, localmount string, cephopts string, mounts *MountManager) cephDriver { d := cephDriver{ - volumeDriver: newVolumeDriver(root), + volumeDriver: newVolumeDriver(root, mounts), username: username, password: password, context: context, diff --git a/netshare/drivers/cifs.go b/netshare/drivers/cifs.go index 59ad6f6..b29b70c 100644 --- a/netshare/drivers/cifs.go +++ b/netshare/drivers/cifs.go @@ -50,9 +50,9 @@ func NewCifsCredentials(user, pass, domain, security, fileMode, dirMode string) } // NewCIFSDriver creating the cifs driver -func NewCIFSDriver(root string, creds *CifsCreds, netrc, cifsopts string) CifsDriver { +func NewCIFSDriver(root string, creds *CifsCreds, netrc, cifsopts string, mounts *MountManager) CifsDriver { d := CifsDriver{ - volumeDriver: newVolumeDriver(root), + volumeDriver: newVolumeDriver(root, mounts), creds: creds, netrc: parseNetRC(netrc), cifsopts: map[string]string{}, diff --git a/netshare/drivers/driver.go b/netshare/drivers/driver.go index 3d822f7..63aab32 100644 --- a/netshare/drivers/driver.go +++ b/netshare/drivers/driver.go @@ -9,14 +9,14 @@ import ( type volumeDriver struct { root string - mountm *mountManager + mountm *MountManager m *sync.Mutex } -func newVolumeDriver(root string) volumeDriver { +func newVolumeDriver(root string, mounts *MountManager) volumeDriver { return volumeDriver{ root: root, - mountm: NewVolumeManager(), + mountm: mounts, m: &sync.Mutex{}, } } diff --git a/netshare/drivers/efs.go b/netshare/drivers/efs.go index 2955ff5..8e8d70f 100644 --- a/netshare/drivers/efs.go +++ b/netshare/drivers/efs.go @@ -22,10 +22,10 @@ type efsDriver struct { dnscache map[string]string } -func NewEFSDriver(root, nameserver string, resolve bool) efsDriver { +func NewEFSDriver(root, nameserver string, resolve bool, mounts *MountManager) efsDriver { d := efsDriver{ - volumeDriver: newVolumeDriver(root), + volumeDriver: newVolumeDriver(root, mounts), resolve: resolve, dnscache: map[string]string{}, } diff --git a/netshare/drivers/mounts.go b/netshare/drivers/mounts.go index 7140a13..42ee67f 100644 --- a/netshare/drivers/mounts.go +++ b/netshare/drivers/mounts.go @@ -2,9 +2,13 @@ package drivers import ( "errors" + "context" + "strings" + log "github.com/sirupsen/logrus" "github.com/docker/go-plugins-helpers/volume" - "strings" + "github.com/docker/docker/client" + "github.com/docker/docker/api/types" ) const ( @@ -20,22 +24,22 @@ type mount struct { managed bool } -type mountManager struct { +type MountManager struct { mounts map[string]*mount } -func NewVolumeManager() *mountManager { - return &mountManager{ +func NewVolumeManager() *MountManager { + return &MountManager{ mounts: map[string]*mount{}, } } -func (m *mountManager) HasMount(name string) bool { +func (m *MountManager) HasMount(name string) bool { _, found := m.mounts[name] return found } -func (m *mountManager) HasOptions(name string) bool { +func (m *MountManager) HasOptions(name string) bool { c, found := m.mounts[name] if found { return c.opts != nil && len(c.opts) > 0 @@ -43,7 +47,7 @@ func (m *mountManager) HasOptions(name string) bool { return false } -func (m *mountManager) HasOption(name, key string) bool { +func (m *MountManager) HasOption(name, key string) bool { if m.HasOptions(name) { if _, ok := m.mounts[name].opts[key]; ok { return ok @@ -52,7 +56,7 @@ func (m *mountManager) HasOption(name, key string) bool { return false } -func (m *mountManager) GetOptions(name string) map[string]string { +func (m *MountManager) GetOptions(name string) map[string]string { if m.HasOptions(name) { c, _ := m.mounts[name] return c.opts @@ -60,7 +64,7 @@ func (m *mountManager) GetOptions(name string) map[string]string { return map[string]string{} } -func (m *mountManager) GetOption(name, key string) string { +func (m *MountManager) GetOption(name, key string) string { if m.HasOption(name, key) { v, _ := m.mounts[name].opts[key] return v @@ -68,7 +72,7 @@ func (m *mountManager) GetOption(name, key string) string { return "" } -func (m *mountManager) GetOptionAsBool(name, key string) bool { +func (m *MountManager) GetOptionAsBool(name, key string) bool { rv := strings.ToLower(m.GetOption(name, key)) if rv == "yes" || rv == "true" { return true @@ -76,12 +80,12 @@ func (m *mountManager) GetOptionAsBool(name, key string) bool { return false } -func (m *mountManager) IsActiveMount(name string) bool { +func (m *MountManager) IsActiveMount(name string) bool { c, found := m.mounts[name] return found && c.connections > 0 } -func (m *mountManager) Count(name string) int { +func (m *MountManager) Count(name string) int { c, found := m.mounts[name] if found { return c.connections @@ -89,7 +93,7 @@ func (m *mountManager) Count(name string) int { return 0 } -func (m *mountManager) Add(name, hostdir string) { +func (m *MountManager) Add(name, hostdir string) { _, found := m.mounts[name] if found { m.Increment(name) @@ -98,7 +102,7 @@ func (m *mountManager) Add(name, hostdir string) { } } -func (m *mountManager) Create(name, hostdir string, opts map[string]string) *mount { +func (m *MountManager) Create(name, hostdir string, opts map[string]string) *mount { c, found := m.mounts[name] if found && c.connections > 0 { c.opts = opts @@ -110,10 +114,13 @@ func (m *mountManager) Create(name, hostdir string, opts map[string]string) *mou } } -func (m *mountManager) Delete(name string) error { - log.Debugf("Delete volume: %s, connections: %d", name, m.Count(name)) +func (m *MountManager) Delete(name string) error { + // Check if any stopped containers are having references with volume. + refCount := checkReferences(name) + log.Debugf("Reference count %d", refCount) if m.HasMount(name) { - if m.Count(name) < 1 { + if m.Count(name) < 1 && refCount < 1 { + log.Debugf("Delete volume: %s, connections: %d", name, m.Count(name)) delete(m.mounts, name) return nil } @@ -122,7 +129,7 @@ func (m *mountManager) Delete(name string) error { return nil } -func (m *mountManager) DeleteIfNotManaged(name string) error { +func (m *MountManager) DeleteIfNotManaged(name string) error { if m.HasMount(name) && !m.IsActiveMount(name) && !m.mounts[name].managed { log.Infof("Removing un-managed volume") return m.Delete(name) @@ -130,24 +137,30 @@ func (m *mountManager) DeleteIfNotManaged(name string) error { return nil } -func (m *mountManager) Increment(name string) int { +func (m *MountManager) Increment(name string) int { + log.Infof("Incrementing for %s", name) c, found := m.mounts[name] + log.Infof("Previous connections state : %d", c.connections) if found { c.connections++ + log.Infof("Current connections state : %d", c.connections) return c.connections } return 0 } -func (m *mountManager) Decrement(name string) int { +func (m *MountManager) Decrement(name string) int { + log.Infof("Decrementing for %s", name) c, found := m.mounts[name] + log.Infof("Previous connections state : %d", c.connections) if found && c.connections > 0 { c.connections-- + log.Infof("Current connections state : %d", c.connections) } return 0 } -func (m *mountManager) GetVolumes(rootPath string) []*volume.Volume { +func (m *MountManager) GetVolumes(rootPath string) []*volume.Volume { volumes := []*volume.Volume{} @@ -156,3 +169,35 @@ func (m *mountManager) GetVolumes(rootPath string) []*volume.Volume { } return volumes } + +func (m *MountManager) AddMount(name string, hostdir string, connections int) { + m.mounts[name] = &mount{name: name, hostdir: hostdir, managed: true, connections: connections} +} + +//Checking volume references with started and stopped containers as well. +func checkReferences(volumeName string) int { + + cli, err := client.NewEnvClient() + if err != nil { + log.Error(err) + } + + var counter = 0 + ContainerListResponse, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) // All : true will return the stopped containers as well. + if err != nil { + log.Fatal(err,". Use -a flag to setup the DOCKER_API_VERSION. Run 'docker-volume-netshare --help' for usage.") + } + + for _, container := range ContainerListResponse { + if len(container.Mounts) == 0 { + continue + } + for _, mounts := range container.Mounts { + if !(mounts.Name == volumeName) { + continue + } + counter++ + } + } + return counter +} \ No newline at end of file diff --git a/netshare/drivers/nfs.go b/netshare/drivers/nfs.go index 9639fd6..5c57765 100644 --- a/netshare/drivers/nfs.go +++ b/netshare/drivers/nfs.go @@ -5,8 +5,8 @@ import ( "os" "path/filepath" - log "github.com/sirupsen/logrus" "github.com/docker/go-plugins-helpers/volume" + log "github.com/sirupsen/logrus" ) const ( @@ -24,9 +24,9 @@ var ( EmptyMap = map[string]string{} ) -func NewNFSDriver(root string, version int, nfsopts string) nfsDriver { +func NewNFSDriver(root string, version int, nfsopts string, mounts *MountManager) nfsDriver { d := nfsDriver{ - volumeDriver: newVolumeDriver(root), + volumeDriver: newVolumeDriver(root, mounts), version: version, nfsopts: map[string]string{}, } @@ -55,13 +55,17 @@ func (n nfsDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) } } - if n.mountm.HasMount(resolvedName) && n.mountm.Count(resolvedName) > 0 { + if n.mountm.HasMount(resolvedName) { log.Infof("Using existing NFS volume mount: %s", hostdir) n.mountm.Increment(resolvedName) if err := run(fmt.Sprintf("grep -c %s /proc/mounts", hostdir)); err != nil { log.Infof("Existing NFS volume not mounted, force remount.") + // maintain count + if n.mountm.Count(resolvedName) > 0 { + n.mountm.Decrement(resolvedName) + } } else { - n.mountm.Increment(resolvedName) + //n.mountm.Increment(resolvedName) return &volume.MountResponse{Mountpoint: hostdir}, nil } } @@ -69,6 +73,9 @@ func (n nfsDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) log.Infof("Mounting NFS volume %s on %s", source, hostdir) if err := createDest(hostdir); err != nil { + if n.mountm.Count(resolvedName) > 0 { + n.mountm.Decrement(resolvedName) + } return nil, err } @@ -79,6 +86,7 @@ func (n nfsDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) n.mountm.Add(resolvedName, hostdir) if err := n.mountVolume(resolvedName, source, hostdir, n.version); err != nil { + n.mountm.Decrement(resolvedName) return nil, err } @@ -86,6 +94,7 @@ func (n nfsDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) log.Infof("Mount: Share and Create options enabled - using %s as sub-dir mount", resolvedName) datavol := filepath.Join(hostdir, resolvedName) if err := createDest(filepath.Join(hostdir, resolvedName)); err != nil { + n.mountm.Decrement(resolvedName) return nil, err } hostdir = datavol diff --git a/netshare/netshare.go b/netshare/netshare.go index 62566ff..902d837 100644 --- a/netshare/netshare.go +++ b/netshare/netshare.go @@ -1,6 +1,7 @@ package netshare import ( + "context" "fmt" "os" "path/filepath" @@ -8,8 +9,11 @@ import ( "syscall" "github.com/ContainX/docker-volume-netshare/netshare/drivers" - log "github.com/sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" "github.com/docker/go-plugins-helpers/volume" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -37,6 +41,7 @@ const ( CephPort = "port" CephOpts = "options" ServerMount = "servermount" + DockerEngineAPI = "dockerapiversion" EnvSambaUser = "NETSHARE_CIFS_USERNAME" EnvSambaPass = "NETSHARE_CIFS_PASSWORD" EnvSambaWG = "NETSHARE_CIFS_DOMAIN" @@ -114,6 +119,7 @@ func setupFlags() { rootCmd.PersistentFlags().Bool(TCPFlag, false, "Bind to TCP rather than Unix sockets. Can also be set via NETSHARE_TCP_ENABLED") rootCmd.PersistentFlags().String(PortFlag, ":8877", "TCP Port if --tcp flag is true. :PORT for all interfaces or ADDRESS:PORT to bind.") rootCmd.PersistentFlags().Bool(VerboseFlag, false, "Turns on verbose logging") + rootCmd.PersistentFlags().StringP(DockerEngineAPI, "a", "", "Docker Engine API Version. Default to latest stable.") cifsCmd.Flags().StringP(UsernameFlag, "u", "", "Username to use for mounts. Can also set environment NETSHARE_CIFS_USERNAME") cifsCmd.Flags().StringP(PasswordFlag, "p", "", "Password to use for mounts. Can also set environment NETSHARE_CIFS_PASSWORD") @@ -148,6 +154,14 @@ func setupLogger(cmd *cobra.Command, args []string) { } } +func setDockerEnv() { + api, _ := rootCmd.PersistentFlags().GetString(DockerEngineAPI) + if api != "" { + os.Setenv("DOCKER_API_VERSION", api) + log.Infof("DOCKER_API_VERSION: %s", api) + } +} + func execCEPH(cmd *cobra.Command, args []string) { username, _ := cmd.Flags().GetString(NameFlag) password, _ := cmd.Flags().GetString(SecretFlag) @@ -156,7 +170,7 @@ func execCEPH(cmd *cobra.Command, args []string) { cephport, _ := cmd.Flags().GetString(CephPort) servermount, _ := cmd.Flags().GetString(ServerMount) cephopts, _ := cmd.Flags().GetString(CephOpts) - + setDockerEnv() if len(username) > 0 { username = "name=" + username } @@ -166,12 +180,14 @@ func execCEPH(cmd *cobra.Command, args []string) { if len(context) > 0 { context = "context=" + "\"" + context + "\"" } - d := drivers.NewCephDriver(rootForType(drivers.CEPH), username, password, context, cephmount, cephport, servermount, cephopts) + mount := syncDockerState("ceph") + d := drivers.NewCephDriver(rootForType(drivers.CEPH), username, password, context, cephmount, cephport, servermount, cephopts, mount) start(drivers.CEPH, d) } func execNFS(cmd *cobra.Command, args []string) { version, _ := cmd.Flags().GetInt(VersionFlag) + setDockerEnv() if os.Getenv(EnvNfsVers) != "" { if v, err := strconv.Atoi(os.Getenv(EnvNfsVers)); err == nil { if v == 3 || v == 4 { @@ -180,7 +196,8 @@ func execNFS(cmd *cobra.Command, args []string) { } } options, _ := cmd.Flags().GetString(OptionsFlag) - d := drivers.NewNFSDriver(rootForType(drivers.NFS), version, options) + mount := syncDockerState("nfs") + d := drivers.NewNFSDriver(rootForType(drivers.NFS), version, options, mount) startOutput(fmt.Sprintf("NFS Version %d :: options: '%s'", version, options)) start(drivers.NFS, d) } @@ -188,7 +205,9 @@ func execNFS(cmd *cobra.Command, args []string) { func execEFS(cmd *cobra.Command, args []string) { resolve, _ := cmd.Flags().GetBool(NoResolveFlag) ns, _ := cmd.Flags().GetString(NameServerFlag) - d := drivers.NewEFSDriver(rootForType(drivers.EFS), ns, !resolve) + setDockerEnv() + mount := syncDockerState("efs") + d := drivers.NewEFSDriver(rootForType(drivers.EFS), ns, !resolve, mount) startOutput(fmt.Sprintf("EFS :: resolve: %v, ns: %s", resolve, ns)) start(drivers.EFS, d) } @@ -203,9 +222,11 @@ func execCIFS(cmd *cobra.Command, args []string) { netrc, _ := cmd.Flags().GetString(NetRCFlag) options, _ := cmd.Flags().GetString(OptionsFlag) + setDockerEnv() creds := drivers.NewCifsCredentials(user, pass, domain, security, fileMode, dirMode) - d := drivers.NewCIFSDriver(rootForType(drivers.CIFS), creds, netrc, options) + mount := syncDockerState("cifs") + d := drivers.NewCIFSDriver(rootForType(drivers.CIFS), creds, netrc, options, mount) if len(user) > 0 { startOutput(fmt.Sprintf("CIFS :: %s, opts: %s", creds, options)) } else { @@ -262,3 +283,59 @@ func isTCPEnabled() bool { } return false } + +func syncDockerState(driverName string) *drivers.MountManager { + log.Infof("Checking for the references of volumes in docker daemon.") + mount := newMountManager() + cli, err := client.NewEnvClient() + if err != nil { + log.Error(err) + } + + volumes, err := cli.VolumeList(context.Background(), filters.Args{}) + if err != nil { + log.Fatal(err, ". Use -a flag to setup the DOCKER_API_VERSION. Run 'docker-volume-netshare --help' for usage.") + } + + for _, vol := range volumes.Volumes { + if !(vol.Driver == driverName) { + continue + } + connections := activeConnections(vol.Name) + log.Infof("Recovered state: %s , %s , %s , %s , %d ", vol.Name, vol.Mountpoint, vol.Driver, vol.CreatedAt, connections) + mount.AddMount(vol.Name, vol.Mountpoint, connections) + } + return mount +} + +func newMountManager() *drivers.MountManager { + mount := drivers.NewVolumeManager() + return mount +} + +// The number of running containers using Volume +func activeConnections(volumeName string) int { + cli, err := client.NewEnvClient() + + if err != nil { + log.Error(err) + } + var counter = 0 + ContainerListResponse, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) //Only check the running containers using volume + if err != nil { + log.Fatal(err, ". Use -a flag to setup the DOCKER_API_VERSION. Run 'docker-volume-netshare --help' for usage.") + } + + for _, container := range ContainerListResponse { + if len(container.Mounts) == 0 { + continue + } + for _, mounts := range container.Mounts { + if !(mounts.Name == volumeName) { + continue + } + counter++ + } + } + return counter +}