Skip to content

Instantly share code, notes, and snippets.

@hackintoshrao
Last active February 2, 2017 13:01
Show Gist options
  • Select an option

  • Save hackintoshrao/3a9e2c77c1ea9cfc5350d1805756aa31 to your computer and use it in GitHub Desktop.

Select an option

Save hackintoshrao/3a9e2c77c1ea9cfc5350d1805756aa31 to your computer and use it in GitHub Desktop.
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/go-plugins-helpers/volume"
)
// Used for Plugin discovery.
// Docker identifies the existence of an active plugin process by searching for
// a unit socket file (.sock) in /run/docker/plugins/.
// A unix server is started at the `socketAdress` to enable discovery of this plugin by docker.
const (
socketAddress = "/run/docker/plugins/hello.sock"
defaultLocation = "us-east-1"
)
// `serverconfig` struct is used to store configuration values of the remote Minio server.
// Minfs uses this info to the mount the remote bucket.
// The server info (endpoint, accessKey and secret Key) is passed during creating a docker volume.
// Here is how to do it,
// $ docker volume create -d minfs \
// --name medical-imaging-store \
// -o endpoint=https://play.minio.io:9000 -o access-key=Q3AM3UQ867SPQQA43P2F\
// -o secret-key=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG -o bucket=test-bucket
type serverConfig struct {
// Endpoint of the remote Minio server.
endpoint string
// `minfs` mounts the remote bucket to a the local `mountpoint`.
bucket string
// accessKey of the remote minio server.
accessKey string
// secretKey of the remote Minio server.
secretKey string
}
// Represents an instance of `minfs` mount of remote Minio bucket.
// Its defined by
// - The server info of the mount.
// - The local mountpoint.
// - The number of connections alive for the mount (No.Of.Services still using the mount point).
type mountInfo struct {
config serverConfig
mountPoint string
// the number of containers using the mount.
// an active mount is done when the count is 0.
// unmount is done only if the number of connections is 0.
// otherwise just the count is decreased.
connections int
}
// minfsDriver - The struct implements the `github.com/docker/go-plugins-helpers/volume.Driver` interface.
// Here are the sequence of events that defines the interaction between docker and the plugin server.
// 1. The driver implements the interface defined in `github.com/docker/go-plugins-helpers/volume.Driver`.
// In our case the struct `minfsDriver` implements the interface.
// 2. Create a new instance of `minfsDriver` and register it with the `go-plugin-helper`.
// `go-plugin-helper` is a tool built to make development of docker plugins easier, visit https://github.com/docker/go-plugins-helpers/.
// The registration is done using https://godoc.org/github.com/docker/go-plugins-helpers/volume#NewHandler .
// 3. Docker interacts with the plugin server via HTTP endpoints whose
// protocols defined here https://docs.docker.com/engine/extend/plugins_volume/#/volumedrivercreate.
// 4. Once registered the implemented methods on `minfsDriver` are called whenever docker
// interacts with the plugin via HTTP requests. These methods are resposible for responding to docker with
// success or error messages.
type minfsDriver struct {
// used for atomic access to the fields.
sync.RWMutex
mountRoot string
// config of the remote Minio server.
config serverConfig
// the local path to which the remote Minio bucket is mounted to.
// An active volume driver server can be used to mount multiple
// remote buckets possibly even referring to different Minio server
// instances or buckets.
// The state info of these mounts are maintained here.
mounts map[string]*mountInfo
}
// return a new instance of minfsDriver.
func newMinfsDriver(mountRoot string) *minfsDriver {
logrus.WithField("method", "new minfs driver").Debug(mountRoot)
d := &minfsDriver{
mountRoot: mountRoot,
config: serverConfig{},
mounts: make(map[string]*mountInfo),
}
return d
}
// *minfsDriver.Create - This method is called by docker when a volume is created
// using `$docker volume create -d <plugin-name> --name <volume-name>`.
// the name (--name) of the plugin uniquely identifies the mount.
// The name of the plugin is passed by docker to the plugin during the HTTP call, check
// https://docs.docker.com/engine/extend/plugins_volume/#/volumedrivercreate for more details.
// Additional options can be passed only during call to `Create`,
// $ docker volume create -d <plugin-name> --name <volume-name> -o <option-key>=<option-value>
// The name of the volume uniquely identifies the mount.
// The remote bucket will be mounted at `mountRoot + volumeName`.
// mountRoot is passed as `--mountroot` flag when starting the plugin server.
func (d *minfsDriver) Create(r volume.Request) volume.Response {
logrus.WithField("method", "Create").Debugf("%#v", r)
// hold lock for safe access.
d.Lock()
defer d.Unlock()
// validate the inputs.
// verify that the name of the volume is not empty.
if r.Name == "" {
return errorResponse("Name of the driver cannot be empty.Use `$ docker volume create -d <plugin-name> --name <volume-name>`")
}
// if the volume is already created verify that the server configs match.
// If not return with error.
// Since the plugin system identifies a mount uniquely by its name,
// its not possible to create a duplicate volume pointing to a different Minio server or bucket.
if _, ok := d.mounts[r.Name]; ok {
// Since the volume by the given name already exists,
// match to see whether the endpoint, bucket, accessKey and secretKey of the
// new request and the existing entry match.
// return error on mismatch.
// else return with success message,
// Since the volume already exists no need to proceed further.
// return success since the volume exists and the configs match.
return volume.Response{}
}
mntInfo := &mountInfo{}
config := serverConfig{}
// mountpoint is the local path where the remote bucket is mounted.
// `mountroot` is passed as an argument while starting the server with `--mountroot` option.
// the given bucket is mounted locally at path `mountroot + volume (r.Name is the name of the volume passed by docker when a volume is created).
mountpoint := filepath.Join(d.mountRoot, r.Name)
// cache the info.
mntInfo.mountPoint = mountpoint
// `Create` is the only function which has the abiility to pass additional options.
// Protocol doc: https://docs.docker.com/engine/extend/plugins_volume/#/volumedrivercreate
// the server config info which is required for the mount later is also passed as an option during create.
// This has to be cached for further usage.
mntInfo.config = config
// `r.Name` contains the plugin name passed with `--name` in `$ docker volume create -d <plugin-name> --name <volume-name>`.
// Name of the volume uniquely identifies the mount.
d.mounts[r.Name] = mntInfo
return volume.Response{}
}
// minfsDriver.Remove - Delete the specified volume from disk.
// This request is issued when a user invokes `docker rm -v` to remove volumes associated with a container.
// Protocol doc: https://docs.docker.com/engine/extend/plugins_volume/#/volumedriverremove
func (d *minfsDriver) Remove(r volume.Request) volume.Response {
logrus.WithField("method", "remove").Debugf("%#v", r)
d.Lock()
defer d.Unlock()
v, ok := d.mounts[r.Name]
// volume doesn't exist in the entry.
// log and return error to docker daemon.
if !ok {
logrus.WithFields(logrus.Fields{
"operation": "Remove",
"volume": r.Name,
}).Error("Volume not found.")
return errorResponse(fmt.Sprintf("volume %s not found", r.Name))
}
// The volume should be under use by any other containers.
// verify if the number of connections is 0.
if v.connections == 0 {
// if the count of existing connections is 0, delete the entry for the volume.
if err := os.RemoveAll(v.mountPoint); err != nil {
return errorResponse(err.Error())
}
// Delete the entry for the mount.
delete(d.mounts, r.Name)
return volume.Response{}
}
// volume is being used by one or more containers.
// log and return error to docker daemon.
logrus.WithFields(logrus.Fields{
"volume": r.Name,
}).Errorf("Volume is currently used by %d containers. ", v.connections)
return errorResponse(fmt.Sprintf("volume %s is currently under use.", r.Name))
}
// *minfsDriver.Path - Respond with the path on the host filesystem where the bucket mount has been made available.
// protocol doc: https://docs.docker.com/engine/extend/plugins_volume/#/volumedriverpath
func (d *minfsDriver) Path(r volume.Request) volume.Response {
logrus.WithField("method", "path").Debugf("%#v", r)
d.RLock()
defer d.RUnlock()
v, ok := d.mounts[r.Name]
if !ok {
logrus.WithFields(logrus.Fields{
"operation": "path",
"volume": r.Name,
}).Error("Volume not found.")
return errorResponse(fmt.Sprintf("volume %s not found", r.Name))
}
return volume.Response{Mountpoint: v.mountPoint}
}
// *minfsDriver.Mount - Does mounting of `minfs`.
// protocol doc: https://docs.docker.com/engine/extend/plugins_volume/#/volumedrivermount
// If the mount alredy exists just increment the number of connections and return.
// Mount is called only when another container shares the created volume.
// Step 1: Create volume.
// $ docker volume create -d minfs-plugin \
// --name profile-pic-store \
// -o endpoint=https://play.minio.io:9000/rao -o access_key=Q3AM3UQ867SPQQA43P2F\
// -o secret-key=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG -o bucket=test-bucket.
// Step 2: Shared the new volume.
// ex: docker run -it -v profile-pic-store:/data busybox /bin/sh
// This is when the Mount operation is called.
// The above set of operations create a mount of remote bucket `test-bucket`,
// in the local path of `mountroot + profile-pic-store`.
// Note: mountroot passed as --mountroot flag while starting the plugin server.
func (d *minfsDriver) Mount(r volume.MountRequest) volume.Response {
logrus.WithField("method", "mount").Debugf("%#v", r)
d.Lock()
defer d.Unlock()
// verify if the volume exists.
// Mount operation should be performed only after creating the bucket.
v, ok := d.mounts[r.Name]
if !ok {
logrus.WithFields(logrus.Fields{
"operation": "mount",
"volume": r.Name,
}).Error("Volume not found.")
return errorResponse(fmt.Sprintf("volume %s not found", r.Name))
}
// create the directory for the mountpoint.
// This will be the directory at which the remote bucket will be mounted.
err := createDir(v.mountPoint)
if err != nil {
logrus.WithFields(logrus.Fields{
"mountpount": v.mountPoint,
}).Fatalf("Error creating directory for the mountpoint. <ERROR> %v.", err)
return errorResponse(err.Error())
}
// If the mountpoint is already under use just increment the counter of usage and return to docker daemon.
if v.connections > 0 {
v.connections++
return volume.Response{Mountpoint: v.mountPoint}
}
return volume.Response{Mountpoint: v.mountPoint}
}
// *minfsDriver.Unmount - unmounts the mount at `mountpoint`.
// protocol doc: https://docs.docker.com/engine/extend/plugins_volume/#/volumedriverunmount
// Unmount is called when a container using the mounted volume is stopped.
func (d *minfsDriver) Unmount(r volume.UnmountRequest) volume.Response {
logrus.WithField("method", "unmount").Debugf("%#v", r)
d.Lock()
defer d.Unlock()
// verify if the mount exists.
v, ok := d.mounts[r.Name]
if !ok {
// mount doesn't exist, return error.
logrus.WithFields(logrus.Fields{
"operation": "unmount",
"volume": r.Name,
}).Error("Volume not found.")
return errorResponse(fmt.Sprintf("volume %s not found", r.Name))
}
// Unmount is done only if no other containers are using the mounted volume.
if v.connections <= 1 {
// unmount.
v.connections = 0
} else {
// If the count is > 1, that is if the mounted volume is already being used by
// another container, dont't unmount, just decrease the count and return.
v.connections--
}
return volume.Response{}
}
// *minfsDriver.Get - Get the mount info.
// protocol doc: https://docs.docker.com/engine/extend/plugins_volume/#/volumedriverget
func (d *minfsDriver) Get(r volume.Request) volume.Response {
logrus.WithField("method", "get").Debugf("%#v", r)
d.Lock()
defer d.Unlock()
// verify if the mount exists.
v, ok := d.mounts[r.Name]
if !ok {
// mount doesn't exist, return error.
logrus.WithFields(logrus.Fields{
"operation": "unmount",
"volume": r.Name,
}).Error("Volume not found.")
return errorResponse(fmt.Sprintf("volume %s not found", r.Name))
}
return volume.Response{Volume: &volume.Volume{Name: r.Name, Mountpoint: v.mountPoint}}
}
// *minfsDriver.List - Get the list of existing volumes.
// protocol doc: https://docs.docker.com/engine/extend/plugins_volume/#/volumedriverlist
func (d *minfsDriver) List(r volume.Request) volume.Response {
logrus.WithField("method", "list").Debugf("%#v", r)
d.Lock()
defer d.Unlock()
var vols []*volume.Volume
for name, v := range d.mounts {
vols = append(vols, &volume.Volume{Name: name, Mountpoint: v.mountPoint})
}
return volume.Response{Volumes: vols}
}
// *minfsDriver.Capabilities - Takes values "local" or "global", more info in protocol doc below.
// protocol doc: https://docs.docker.com/engine/extend/plugins_volume/#/volumedrivercapabilities
func (d *minfsDriver) Capabilities(r volume.Request) volume.Response {
logrus.WithField("method", "capabilities").Debugf("%#v", r)
return volume.Response{Capabilities: volume.Capability{Scope: "local"}}
}
// mounts minfs to the local mountpoint.
func (d *minfsDriver) mountVolume(v mountInfo) error {
// URL for the bucket (ex: https://play.minio.io:9000/mybucket).
var bucketPath string
if strings.HasSuffix(v.config.endpoint, "/") {
bucketPath = v.config.endpoint + v.config.bucket
} else {
bucketPath = v.config.endpoint + "/" + v.config.bucket
}
// mount command for minfs.
// ex: mount -t minfs https://play.minio.io:9000/testbucket /testbucket
cmd := fmt.Sprintf("mount -t minfs %s %s", bucketPath, v.mountPoint)
logrus.Debug(cmd)
return exec.Command("sh", "-c", cmd).Run()
}
// executes `unmount` on the specified volume.
func (d *minfsDriver) unmountVolume(target string) error {
// Unmount the volume.
cmd := fmt.Sprintf("umount %s", target)
logrus.Debug(cmd)
return exec.Command("sh", "-c", cmd).Run()
}
func main() {
// --mountroot flag defines the root folder where are the volumes are mounted.
// If the option is not specified '/tmp' is taken as default mount root.
mountRoot := flag.String("mountroot", "/tmp", "root for mouting Minio buckets.")
flag.Parse()
// check if the mount root exists.
// create if it doesn't exist.
err := createDir(*mountRoot)
if err != nil {
logrus.WithFields(logrus.Fields{
"mountroot": mountRoot,
}).Fatalf("Unable to create mountroot.")
return
}
// if `export DEBUG=1` is set, debug logs will be printed.
debug := os.Getenv("DEBUG")
if ok, _ := strconv.ParseBool(debug); ok {
logrus.SetLevel(logrus.DebugLevel)
}
// Create a new instance MinfsDriver.
// The struct implements the `github.com/docker/go-plugins-helpers/volume.Driver` interface.
d := newMinfsDriver(*mountRoot)
// register it with the `go-plugin-helper`.
// `go-plugin-helper` is a tool built to make development of docker plugins easier, visit https://github.com/docker/go-plugins-helpers/.
// The registration is done using https://godoc.org/github.com/docker/go-plugins-helpers/volume#NewHandler .
h := volume.NewHandler(d)
// create a server on unix socket.
logrus.Infof("listening on %s", socketAddress)
logrus.Error(h.ServeUnix(socketAddress, 0))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment