├── .gitignore ├── LICENSE ├── Makefile ├── NEWS ├── README.md ├── docker-volume-beegfs.service ├── driver.go ├── files └── post-install-systemd ├── main.go └── vendor └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | docker-volume-beegfs 3 | *.rpm 4 | *.deb 5 | obj 6 | .idea 7 | vendor/*/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Red Cool Beans 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: deps compile 2 | 3 | VERSION=0.4.0 4 | 5 | compile: 6 | go build 7 | 8 | deps: 9 | go get 10 | 11 | fmt: 12 | gofmt -s -w -l . 13 | 14 | dist: rpm deb 15 | 16 | rpm-deps: 17 | yum install -y ruby ruby-devel rubygems rpm-build make go git 18 | gem install fpm 19 | 20 | rpm: compile rpm-deps 21 | mkdir -p obj/redhat/usr/bin 22 | mkdir -p obj/redhat/lib/systemd/system/ 23 | install -m 0755 docker-volume-beegfs obj/redhat/usr/bin 24 | install -m 0644 docker-volume-beegfs.service obj/redhat/lib/systemd/system 25 | fpm -C obj/redhat --vendor RedCoolBeans -m "info@redcoolbeans.com" -f \ 26 | -s dir -t rpm -n docker-volume-beegfs \ 27 | --after-install files/post-install-systemd --version ${VERSION} . && \ 28 | rm -fr obj/redhat 29 | 30 | # builds are done on RHEL, when building locally on Debian use the following: 31 | # apt-get install -y ruby ruby-dev gcc golang git make 32 | deb-deps: 33 | yum install -y ruby ruby-devel rubygems rpm-build make go git 34 | gem install fpm 35 | 36 | deb: compile deb-deps 37 | mkdir -p obj/debian/usr/bin 38 | mkdir -p obj/debian/lib/systemd/system/ 39 | install -m 0755 docker-volume-beegfs obj/debian/usr/bin 40 | install -m 0644 docker-volume-beegfs.service obj/debian/lib/systemd/system 41 | fpm -C obj/debian --vendor RedCoolBeans -m "info@redcoolbeans.com" -f \ 42 | -s dir -t deb -n docker-volume-beegfs \ 43 | --after-install files/post-install-systemd --version ${VERSION} . && \ 44 | rm -fr obj/debian 45 | 46 | clean: 47 | rm -fr obj *.deb *.rpm docker-volume-beegfs 48 | 49 | .PHONY: clean rpm-deps deb-deps fmt deps compile 50 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | # 0.4.0 2 | 3 | - Support for latest version of Docker 4 | - Does need Go 1.8.1 5 | - Implementation of capabilities 6 | 7 | # 0.2.0 8 | 9 | - Migrate to go-plugin-helpers/volume 10 | - Support for Docker 1.10 API 11 | 12 | # 0.1.1 13 | 14 | - logging is much more silent by default (toggle with `-verbose=true`) 15 | - .deb package for systemd-based Debian-like distributions (e.g. Debian 8) 16 | 17 | # 0.1.0 18 | 19 | Initial public release 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-volume-beegfs 2 | 3 | Docker Volume plugin to create persistent volumes in a [BeeGFS](http://www.beegfs.com/content/) cluster. 4 | 5 | ## Preconditions 6 | 7 | - BeeGFS cluster has to be setup and running 8 | - `beegfs-client` service needs to be running on the Docker host 9 | 10 | ## Installation 11 | 12 | A pre-built binary as well as `rpm` and `deb` packages are available from the [releases](https://github.com/RedCoolBeans/docker-volume-beegfs/releases) page. 13 | 14 | ### RedHat/CentOS 7 15 | 16 | An rpm can be built with: 17 | 18 | make rpm 19 | 20 | Then install and start the service: 21 | 22 | yum localinstall docker-volume-beegfs-$VERSION.rpm 23 | systemctl start docker-volume-beegfs 24 | 25 | ### Debian 8 26 | 27 | Debian packages are currently built on a RedHat system, but the `Makefile` 28 | describes which packages to install on Debian when building from scratch. 29 | Building the actual package can be done on a Debian system without Makefile modifications: 30 | 31 | make deb 32 | 33 | Now you can install and start the service: 34 | 35 | dpkg -i docker-volume-beegfs_$VERSION.deb 36 | systemctl start docker-volume-beegfs 37 | 38 | ### From source code 39 | 40 | The plugin uses [govendor](https://github.com/kardianos/govendor) to manage dependencies. 41 | 42 | go get -u github.com/kardianos/govendor 43 | 44 | Restore dependencies: 45 | 46 | govendor sync 47 | 48 | Build the plugin: 49 | 50 | go build 51 | 52 | ## Usage 53 | 54 | First create a volume: 55 | 56 | docker volume create -d beegfs --name postgres-portroach 57 | 58 | Then use the volume by passing the name (`postgres-1`): 59 | 60 | docker run -ti -v postgres-portroach:/var/lib/postgresql/data --volume-driver=beegfs -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres 61 | 62 | Inspect the volume: 63 | 64 | docker volume inspect postgres-portroach 65 | 66 | Remove the volume (note that this will _not_ remove the actual data): 67 | 68 | docker volume rm postgres-portroach 69 | 70 | ### Non-default mount points 71 | 72 | By default BeeGFS uses `/mnt/beegfs` as the mount point (as configured in 73 | `beegfs-mounts.conf`), and this plugin does too. For non-standard mount points 74 | you can specify an alternate root when creating a new volume: 75 | 76 | docker volume create -d beegfs --name b3 -o root=/stor/b3 77 | 78 | Other options are currently silently ignored. 79 | 80 | ## Roadmap 81 | 82 | - No outstanding features/requests. 83 | 84 | ## License 85 | 86 | MIT, please see the LICENSE file. 87 | 88 | ## Contributing 89 | 90 | 1. Fork it 91 | 2. Create your feature branch (`git checkout -b my-new-feature`) 92 | 3. Commit your changes (`git commit -am 'Add some feature'`) 93 | 4. Push to the branch (`git push origin my-new-feature`) 94 | 5. Create new Pull Request 95 | -------------------------------------------------------------------------------- /docker-volume-beegfs.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Docker Volume Plugin for BeeGFS 3 | Documentation=https://github.com/RedCoolBeans/docker-volume-beegfs 4 | After=beegfs-client.service 5 | Requires=beegfs-client.service 6 | 7 | [Service] 8 | ExecStart=/usr/bin/docker-volume-beegfs 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | log "github.com/Sirupsen/logrus" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "sync" 11 | "syscall" 12 | 13 | "github.com/davecgh/go-spew/spew" 14 | "github.com/docker/go-plugins-helpers/volume" 15 | ) 16 | 17 | // A single volume instance 18 | type beegfsMount struct { 19 | name string 20 | path string 21 | root string 22 | } 23 | 24 | type beegfsDriver struct { 25 | mounts map[string]*beegfsMount 26 | m *sync.Mutex 27 | } 28 | 29 | func newBeeGFSDriver(root string) beegfsDriver { 30 | d := beegfsDriver{ 31 | mounts: make(map[string]*beegfsMount), 32 | m: &sync.Mutex{}, 33 | } 34 | 35 | return d 36 | } 37 | 38 | func (b beegfsDriver) Create(r *volume.CreateRequest) error { 39 | var volumeRoot string 40 | 41 | log.Infof("Create: %s, %v", r.Name, r.Options) 42 | 43 | b.m.Lock() 44 | defer b.m.Unlock() 45 | 46 | // Handle options (unrecognized options are silently ignored): 47 | // root: directory to create new volumes (this should correspond with 48 | // beegfs-mounts.conf). 49 | if optsRoot, ok := r.Options["root"]; ok { 50 | volumeRoot = optsRoot 51 | } else { 52 | // Assume the default root 53 | volumeRoot = *root 54 | } 55 | 56 | dest := filepath.Join(volumeRoot, r.Name) 57 | if !isbeegfs(dest) { 58 | emsg := fmt.Sprintf("Cannot create volume %s as it's not on a BeeGFS filesystem", dest) 59 | log.Error(emsg) 60 | return errors.New(emsg) 61 | } 62 | 63 | fmt.Printf("mounts: %d", len(b.mounts)) 64 | if _, ok := b.mounts[r.Name]; ok { 65 | imsg := fmt.Sprintf("Cannot create volume %s, it already exists", dest) 66 | log.Info(imsg) 67 | return nil 68 | } 69 | 70 | volumePath := filepath.Join(volumeRoot, r.Name) 71 | 72 | if err := createDest(dest); err != nil { 73 | return err 74 | } 75 | 76 | b.mounts[r.Name] = &beegfsMount { 77 | name: r.Name, 78 | path: volumePath, 79 | root: volumeRoot, 80 | } 81 | 82 | if *verbose { 83 | spew.Dump(b.mounts) 84 | } 85 | 86 | return nil 87 | } 88 | 89 | func (b beegfsDriver) Remove(r *volume.RemoveRequest) error { 90 | log.Infof("Remove: %s", r.Name) 91 | 92 | b.m.Lock() 93 | defer b.m.Unlock() 94 | 95 | if _, ok := b.mounts[r.Name]; ok { 96 | delete(b.mounts, r.Name) 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func (b beegfsDriver) Path(r *volume.PathRequest) (*volume.PathResponse, error) { 103 | log.Debugf("Path: %s", r.Name) 104 | 105 | if _, ok := b.mounts[r.Name]; ok { 106 | return &volume.PathResponse{Mountpoint: b.mounts[r.Name].path}, nil 107 | } 108 | 109 | return nil, nil 110 | } 111 | 112 | func (b beegfsDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) { 113 | log.Infof("Mount: %s", r.Name) 114 | dest := filepath.Join(b.mounts[r.Name].root, r.Name) 115 | 116 | if !isbeegfs(dest) { 117 | emsg := fmt.Sprintf("Cannot mount volume %s as it's not on a BeeGFS filesystem", dest) 118 | log.Error(emsg) 119 | return nil, errors.New(emsg) 120 | } 121 | 122 | if _, ok := b.mounts[r.Name]; ok { 123 | return &volume.MountResponse{Mountpoint: b.mounts[r.Name].path}, nil 124 | } 125 | 126 | return nil, nil 127 | } 128 | 129 | func (b beegfsDriver) Unmount(r *volume.UnmountRequest) error { 130 | log.Infof("Unmount: %s", r.Name) 131 | return nil 132 | } 133 | 134 | func (b beegfsDriver) Get(r *volume.GetRequest) (*volume.GetResponse, error) { 135 | log.Infof("Get: %s", r.Name) 136 | 137 | if v, ok := b.mounts[r.Name]; ok { 138 | return &volume.GetResponse{ 139 | Volume: &volume.Volume{ 140 | Name: v.name, 141 | Mountpoint: v.path, 142 | }, 143 | }, nil 144 | } 145 | 146 | return nil, errors.New(fmt.Sprintf("volume %s unknown", r.Name)) 147 | } 148 | 149 | func (b beegfsDriver) List() (*volume.ListResponse, error) { 150 | log.Infof("List") 151 | 152 | volumes := []*volume.Volume{} 153 | 154 | for v := range b.mounts { 155 | volumes = append(volumes, &volume.Volume{Name: b.mounts[v].name, Mountpoint: b.mounts[v].path}) 156 | } 157 | 158 | return &volume.ListResponse{Volumes: volumes}, nil 159 | } 160 | 161 | func (d beegfsDriver) Capabilities() *volume.CapabilitiesResponse { 162 | return &volume.CapabilitiesResponse{ 163 | Capabilities: volume.Capability{ 164 | Scope: "global", 165 | }, 166 | } 167 | } 168 | 169 | // Check if the parent directory (where the volume will be created) 170 | // is of type 'beegfs' using the BEEGFS_MAGIC value. 171 | func isbeegfs(volumepath string) bool { 172 | log.Debugf("isbeegfs() for %s", volumepath) 173 | stat := syscall.Statfs_t{} 174 | err := syscall.Statfs(path.Dir(volumepath), &stat) 175 | if err != nil { 176 | log.Errorf("Could not determine filesystem type for %s: %s", volumepath, err) 177 | return false 178 | } 179 | 180 | log.Debugf("Type for %s: %d", volumepath, stat.Type) 181 | 182 | // BEEGFS_MAGIC 0x19830326 183 | return stat.Type == 428016422 184 | } 185 | 186 | func createDest(dest string) error { 187 | fstat, err := os.Lstat(dest) 188 | 189 | if os.IsNotExist(err) { 190 | if err := os.MkdirAll(dest, 0755); err != nil { 191 | return err 192 | } 193 | } else if err != nil { 194 | return err 195 | } 196 | 197 | if fstat != nil && !fstat.IsDir() { 198 | return fmt.Errorf("%v already exist and it's not a directory", dest) 199 | } 200 | 201 | return nil 202 | } 203 | -------------------------------------------------------------------------------- /files/post-install-systemd: -------------------------------------------------------------------------------- 1 | # Enable service 2 | systemctl enable docker-volume-beegfs.service -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | log "github.com/Sirupsen/logrus" 7 | 8 | "github.com/docker/go-plugins-helpers/volume" 9 | "os/user" 10 | "strconv" 11 | ) 12 | 13 | var ( 14 | // This is the path in beegfs-mounts.conf 15 | root = flag.String("root", "/mnt/beegfs", "Base directory where volumes are created in the cluster") 16 | verbose = flag.Bool("verbose", false, "Enable verbose logging") 17 | ) 18 | 19 | func main() { 20 | flag.Parse() 21 | 22 | if *verbose { 23 | log.SetLevel(log.DebugLevel) 24 | } else { 25 | log.SetLevel(log.InfoLevel) 26 | } 27 | 28 | u, _ := user.Lookup("root") 29 | gid, _ := strconv.Atoi(u.Gid) 30 | 31 | d := newBeeGFSDriver(*root) 32 | h := volume.NewHandler(d) 33 | fmt.Println(h.ServeUnix("beegfs", gid)) 34 | } 35 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "", 4 | "package": [ 5 | { 6 | "checksumSHA1": "+yzJjATeaGRYi77nZJgZm0ZvAN8=", 7 | "path": "github.com/Microsoft/go-winio", 8 | "revision": "c4dc1301f1dc0307acd38e611aa375a64dfe0642", 9 | "revisionTime": "2017-07-12T04:46:15Z" 10 | }, 11 | { 12 | "checksumSHA1": "BfvuOnSnlAXPOAZUa3J188NjYH8=", 13 | "path": "github.com/Sirupsen/logrus", 14 | "revision": "51dc0fc64317a2861273909081f9c315786533eb", 15 | "revisionTime": "2017-07-13T11:57:24Z" 16 | }, 17 | { 18 | "checksumSHA1": "jZW0YmSE2j+316qebIB/FcrVZ6s=", 19 | "path": "github.com/coreos/go-systemd/activation", 20 | "revision": "24036eb3df68550d24a2736c5d013f4e83366866", 21 | "revisionTime": "2017-06-09T14:46:27Z" 22 | }, 23 | { 24 | "checksumSHA1": "0y5phFHOrFHf/2w+q/E4FZHQXVI=", 25 | "path": "github.com/davecgh/go-spew/spew", 26 | "revision": "adab96458c51a58dc1783b3335dcce5461522e75", 27 | "revisionTime": "2017-07-11T18:34:51Z" 28 | }, 29 | { 30 | "checksumSHA1": "a2yC46a1qsJomgY6rb+FkTFiqmE=", 31 | "path": "github.com/davecgh/go-spew/spew/testdata", 32 | "revision": "adab96458c51a58dc1783b3335dcce5461522e75", 33 | "revisionTime": "2017-07-11T18:34:51Z" 34 | }, 35 | { 36 | "checksumSHA1": "f6yrnwXO2t4Lx7bqY1U105fSu2U=", 37 | "path": "github.com/docker/go-connections/sockets", 38 | "revision": "3ede32e2033de7505e6500d6c868c2b9ed9f169d", 39 | "revisionTime": "2017-06-23T20:36:43Z" 40 | }, 41 | { 42 | "checksumSHA1": "B+ogl7NoFxawv5DWI1tb3y+rzQg=", 43 | "path": "github.com/docker/go-plugins-helpers/sdk", 44 | "revision": "abb3e589c0322ddc019c2ec5f587ea0df2024869", 45 | "revisionTime": "2017-06-21T09:41:05Z" 46 | }, 47 | { 48 | "checksumSHA1": "JU42zJNdlm52BDonHLSy8l1ZsRs=", 49 | "path": "github.com/docker/go-plugins-helpers/volume", 50 | "revision": "abb3e589c0322ddc019c2ec5f587ea0df2024869", 51 | "revisionTime": "2017-06-21T09:41:05Z" 52 | }, 53 | { 54 | "checksumSHA1": "RZOdTSZN/PgcTqko5LzIAzw+UT4=", 55 | "path": "github.com/pmezard/go-difflib/difflib", 56 | "revision": "792786c7400a136282c1664665ae0a8db921c6c2", 57 | "revisionTime": "2016-01-10T10:55:54Z" 58 | }, 59 | { 60 | "checksumSHA1": "TpQdKdG6Q7yBpMBTecSP6RKj/Iw=", 61 | "path": "github.com/stretchr/testify/assert", 62 | "revision": "05e8a0eda380579888eb53c394909df027f06991", 63 | "revisionTime": "2017-07-13T16:51:06Z" 64 | }, 65 | { 66 | "checksumSHA1": "V5+0rhkF5PahTjwDGBaQTMoz1z0=", 67 | "path": "golang.org/x/net/proxy", 68 | "revision": "f01ecb60fe3835d80d9a0b7b2bf24b228c89260e", 69 | "revisionTime": "2017-07-11T11:58:19Z" 70 | }, 71 | { 72 | "checksumSHA1": "rNuMGFgygEt+Y8BhFd+DQP3vF9k=", 73 | "path": "golang.org/x/sys/unix", 74 | "revision": "4cd6d1a821c7175768725b55ca82f14683a29ea4", 75 | "revisionTime": "2017-07-14T13:21:52Z" 76 | }, 77 | { 78 | "checksumSHA1": "g6REgYaSXbtRNfvEX6HM/hTTz8I=", 79 | "path": "golang.org/x/sys/windows", 80 | "revision": "4cd6d1a821c7175768725b55ca82f14683a29ea4", 81 | "revisionTime": "2017-07-14T13:21:52Z" 82 | } 83 | ], 84 | "rootPath": "github.com/RedCoolBeans/docker-volume-beegfs" 85 | } 86 | --------------------------------------------------------------------------------