mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Create extpoint for graphdrivers
Allows people to create out-of-process graphdrivers that can be used with Docker. Extensions must be started before Docker otherwise Docker will fail to start. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
		
							parent
							
								
									9f517fc5bb
								
							
						
					
					
						commit
						b78e4216a2
					
				
					 10 changed files with 967 additions and 17 deletions
				
			
		| 
						 | 
				
			
			@ -111,6 +111,9 @@ func GetDriver(name, home string, options []string) (Driver, error) {
 | 
			
		|||
	if initFunc, exists := drivers[name]; exists {
 | 
			
		||||
		return initFunc(filepath.Join(home, name), options)
 | 
			
		||||
	}
 | 
			
		||||
	if pluginDriver, err := lookupPlugin(name, home, options); err == nil {
 | 
			
		||||
		return pluginDriver, nil
 | 
			
		||||
	}
 | 
			
		||||
	logrus.Errorf("Failed to GetDriver graph %s %s", name, home)
 | 
			
		||||
	return nil, ErrNotSupported
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										33
									
								
								daemon/graphdriver/plugin.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								daemon/graphdriver/plugin.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
// +build experimental
 | 
			
		||||
// +build daemon
 | 
			
		||||
 | 
			
		||||
package graphdriver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/plugins"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type pluginClient interface {
 | 
			
		||||
	// Call calls the specified method with the specified arguments for the plugin.
 | 
			
		||||
	Call(string, interface{}, interface{}) error
 | 
			
		||||
	// Stream calls the specified method with the specified arguments for the plugin and returns the response IO stream
 | 
			
		||||
	Stream(string, interface{}) (io.ReadCloser, error)
 | 
			
		||||
	// SendFile calls the specified method, and passes through the IO stream
 | 
			
		||||
	SendFile(string, io.Reader, interface{}) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func lookupPlugin(name, home string, opts []string) (Driver, error) {
 | 
			
		||||
	pl, err := plugins.Get(name, "GraphDriver")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Error looking up graphdriver plugin %s: %v", name, err)
 | 
			
		||||
	}
 | 
			
		||||
	return newPluginDriver(name, home, opts, pl.Client)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newPluginDriver(name, home string, opts []string, c pluginClient) (Driver, error) {
 | 
			
		||||
	proxy := &graphDriverProxy{name, c}
 | 
			
		||||
	return proxy, proxy.Init(home, opts)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								daemon/graphdriver/plugin_unsupported.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								daemon/graphdriver/plugin_unsupported.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
// +build !experimental
 | 
			
		||||
 | 
			
		||||
package graphdriver
 | 
			
		||||
 | 
			
		||||
func lookupPlugin(name, home string, opts []string) (Driver, error) {
 | 
			
		||||
	return nil, ErrNotSupported
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										210
									
								
								daemon/graphdriver/proxy.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								daemon/graphdriver/proxy.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,210 @@
 | 
			
		|||
// +build experimental
 | 
			
		||||
// +build daemon
 | 
			
		||||
 | 
			
		||||
package graphdriver
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/archive"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type graphDriverProxy struct {
 | 
			
		||||
	name   string
 | 
			
		||||
	client pluginClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type graphDriverRequest struct {
 | 
			
		||||
	ID         string `json:",omitempty"`
 | 
			
		||||
	Parent     string `json:",omitempty"`
 | 
			
		||||
	MountLabel string `json:",omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type graphDriverResponse struct {
 | 
			
		||||
	Err      string            `json:",omitempty"`
 | 
			
		||||
	Dir      string            `json:",omitempty"`
 | 
			
		||||
	Exists   bool              `json:",omitempty"`
 | 
			
		||||
	Status   [][2]string       `json:",omitempty"`
 | 
			
		||||
	Changes  []archive.Change  `json:",omitempty"`
 | 
			
		||||
	Size     int64             `json:",omitempty"`
 | 
			
		||||
	Metadata map[string]string `json:",omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type graphDriverInitRequest struct {
 | 
			
		||||
	Home string
 | 
			
		||||
	Opts []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Init(home string, opts []string) error {
 | 
			
		||||
	args := &graphDriverInitRequest{
 | 
			
		||||
		Home: home,
 | 
			
		||||
		Opts: opts,
 | 
			
		||||
	}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Init", args, &ret); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) String() string {
 | 
			
		||||
	return d.name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Create(id, parent string) error {
 | 
			
		||||
	args := &graphDriverRequest{
 | 
			
		||||
		ID:     id,
 | 
			
		||||
		Parent: parent,
 | 
			
		||||
	}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Create", args, &ret); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Remove(id string) error {
 | 
			
		||||
	args := &graphDriverRequest{ID: id}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Remove", args, &ret); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Get(id, mountLabel string) (string, error) {
 | 
			
		||||
	args := &graphDriverRequest{
 | 
			
		||||
		ID:         id,
 | 
			
		||||
		MountLabel: mountLabel,
 | 
			
		||||
	}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Get", args, &ret); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		err = errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return ret.Dir, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Put(id string) error {
 | 
			
		||||
	args := &graphDriverRequest{ID: id}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Put", args, &ret); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Exists(id string) bool {
 | 
			
		||||
	args := &graphDriverRequest{ID: id}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Exists", args, &ret); err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return ret.Exists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Status() [][2]string {
 | 
			
		||||
	args := &graphDriverRequest{}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Status", args, &ret); err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return ret.Status
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) GetMetadata(id string) (map[string]string, error) {
 | 
			
		||||
	args := &graphDriverRequest{
 | 
			
		||||
		ID: id,
 | 
			
		||||
	}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.GetMetadata", args, &ret); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return nil, errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return ret.Metadata, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Cleanup() error {
 | 
			
		||||
	args := &graphDriverRequest{}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Cleanup", args, &ret); err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Diff(id, parent string) (archive.Archive, error) {
 | 
			
		||||
	args := &graphDriverRequest{
 | 
			
		||||
		ID:     id,
 | 
			
		||||
		Parent: parent,
 | 
			
		||||
	}
 | 
			
		||||
	body, err := d.client.Stream("GraphDriver.Diff", args)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		body.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return archive.Archive(body), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) Changes(id, parent string) ([]archive.Change, error) {
 | 
			
		||||
	args := &graphDriverRequest{
 | 
			
		||||
		ID:     id,
 | 
			
		||||
		Parent: parent,
 | 
			
		||||
	}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.Changes", args, &ret); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return nil, errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret.Changes, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) ApplyDiff(id, parent string, diff archive.Reader) (int64, error) {
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.SendFile(fmt.Sprintf("GraphDriver.ApplyDiff?id=%s&parent=%s", id, parent), diff, &ret); err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return -1, errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return ret.Size, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *graphDriverProxy) DiffSize(id, parent string) (int64, error) {
 | 
			
		||||
	args := &graphDriverRequest{
 | 
			
		||||
		ID:     id,
 | 
			
		||||
		Parent: parent,
 | 
			
		||||
	}
 | 
			
		||||
	var ret graphDriverResponse
 | 
			
		||||
	if err := d.client.Call("GraphDriver.DiffSize", args, &ret); err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
	if ret.Err != "" {
 | 
			
		||||
		return -1, errors.New(ret.Err)
 | 
			
		||||
	}
 | 
			
		||||
	return ret.Size, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										321
									
								
								experimental/plugins_graphdriver.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								experimental/plugins_graphdriver.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,321 @@
 | 
			
		|||
# Experimental: Docker graph driver plugins
 | 
			
		||||
 | 
			
		||||
Docker graph driver plugins enable admins to use an external/out-of-process
 | 
			
		||||
graph driver for use with Docker engine. This is an alternative to using the
 | 
			
		||||
built-in storage drivers, such as aufs/overlay/devicemapper/btrfs.
 | 
			
		||||
 | 
			
		||||
A graph driver plugin is used for image and container fs storage, as such
 | 
			
		||||
the plugin must be started and available for connections prior to Docker Engine
 | 
			
		||||
being started.
 | 
			
		||||
 | 
			
		||||
# Write a graph driver plugin
 | 
			
		||||
 | 
			
		||||
See the [plugin documentation](/docs/extend/plugins.md) for detailed information
 | 
			
		||||
on the underlying plugin protocol.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Graph Driver plugin protocol
 | 
			
		||||
 | 
			
		||||
If a plugin registers itself as a `GraphDriver` when activated, then it is
 | 
			
		||||
expected to provide the rootfs for containers as well as image layer storage.
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Init
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Home": "/graph/home/path",
 | 
			
		||||
  "Opts": []
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Initialize the graph driver plugin with a home directory and array of options.
 | 
			
		||||
Plugins are not required to accept these options as the Docker Engine does not
 | 
			
		||||
require that the plugin use this path or options, they are only being passed
 | 
			
		||||
through from the user.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Err": null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Create
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187",
 | 
			
		||||
  "Parent": "2cd9c322cb78a55e8212aa3ea8425a4180236d7106938ec921d0935a4b8ca142"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Create a new, empty, filesystem layer with the specified `ID` and `Parent`.
 | 
			
		||||
`Parent` may be an empty string, which would indicate that there is no parent
 | 
			
		||||
layer.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Err: null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Remove
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Remove the filesystem layer with this given `ID`.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Err: null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Get
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187"
 | 
			
		||||
  "MountLabel": ""
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Get the mountpoint for the layered filesystem referred to by the given `ID`.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Dir": "/var/mygraph/46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187",
 | 
			
		||||
  "Err": ""
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with the absolute path to the mounted layered filesystem.
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Put
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Release the system resources for the specified `ID`, such as unmounting the
 | 
			
		||||
filesystem layer.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Err: null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Exists
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Determine if a filesystem layer with the specified `ID` exists.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Exists": true
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with a boolean for whether or not the filesystem layer with the specified
 | 
			
		||||
`ID` exists.
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Status
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Get low-level diagnostic information about the graph driver.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Status": [[]]
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with a 2-D array with key/value pairs for the underlying status
 | 
			
		||||
information.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.GetMetadata
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Get low-level diagnostic information about the layered filesystem with the
 | 
			
		||||
with the specified `ID`
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Metadata": {},
 | 
			
		||||
  "Err": null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with a set of key/value pairs containing the low-level diagnostic
 | 
			
		||||
information about the layered filesystem.
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Cleanup
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Perform neccessary tasks to release resources help by the plugin, for example
 | 
			
		||||
unmounting all the layered file systems.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Err: null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Diff
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187",
 | 
			
		||||
  "Parent": "2cd9c322cb78a55e8212aa3ea8425a4180236d7106938ec921d0935a4b8ca142"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Get an archive of the changes between the filesystem layers specified by the `ID`
 | 
			
		||||
and `Parent`. `Parent` may be an empty string, in which case there is no parent.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{{ TAR STREAM }}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.Changes
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187",
 | 
			
		||||
  "Parent": "2cd9c322cb78a55e8212aa3ea8425a4180236d7106938ec921d0935a4b8ca142"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Get a list of changes between the filesystem layers specified by the `ID` and
 | 
			
		||||
`Parent`. `Parent` may be an empty string, in which case there is no parent.
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Changes": [{}],
 | 
			
		||||
  "Err": null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Responds with a list of changes. The structure of a change is:
 | 
			
		||||
```
 | 
			
		||||
  "Path": "/some/path",
 | 
			
		||||
  "Kind": 0,
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Where teh `Path` is the filesystem path within the layered filesystem that is
 | 
			
		||||
changed and `Kind` is an integer specifying the type of change that occurred:
 | 
			
		||||
 | 
			
		||||
- 0 - Modified
 | 
			
		||||
- 1 - Added
 | 
			
		||||
- 2 - Deleted
 | 
			
		||||
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.ApplyDiff
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{{ TAR STREAM }}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Extract the changeset from the given diff into the layer with the specified `ID`
 | 
			
		||||
and `Parent`
 | 
			
		||||
 | 
			
		||||
**Query Parameters**:
 | 
			
		||||
 | 
			
		||||
- id (required)- the `ID` of the new filesystem layer to extract the diff to
 | 
			
		||||
- parent (required)- the `Parent` of the given `ID`
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Size": 512366,
 | 
			
		||||
  "Err": null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with the size of the new layer in bytes.
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
 | 
			
		||||
### /GraphDriver.DiffSize
 | 
			
		||||
 | 
			
		||||
**Request**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "ID": "46fe8644f2572fd1e505364f7581e0c9dbc7f14640bd1fb6ce97714fb6fc5187",
 | 
			
		||||
  "Parent": "2cd9c322cb78a55e8212aa3ea8425a4180236d7106938ec921d0935a4b8ca142"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Calculate the changes between the specified `ID`
 | 
			
		||||
 | 
			
		||||
**Response**:
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
  "Size": 512366,
 | 
			
		||||
  "Err": null
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Respond with the size changes between the specified `ID` and `Parent`
 | 
			
		||||
Respond with a string error if an error occurred.
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +1,22 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/reexec"
 | 
			
		||||
	"github.com/go-check/check"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Test(t *testing.T) {
 | 
			
		||||
	reexec.Init() // This is required for external graphdriver tests
 | 
			
		||||
 | 
			
		||||
	if !isLocalDaemon {
 | 
			
		||||
		fmt.Println("INFO: Testing against a remote daemon")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("INFO: Testing against a local daemon")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	check.TestingT(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										348
									
								
								integration-cli/docker_cli_external_graphdriver_unix_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								integration-cli/docker_cli_external_graphdriver_unix_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,348 @@
 | 
			
		|||
// +build experimental
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/daemon/graphdriver"
 | 
			
		||||
	"github.com/docker/docker/daemon/graphdriver/vfs"
 | 
			
		||||
	"github.com/docker/docker/pkg/archive"
 | 
			
		||||
	"github.com/go-check/check"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	check.Suite(&DockerExternalGraphdriverSuite{
 | 
			
		||||
		ds: &DockerSuite{},
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DockerExternalGraphdriverSuite struct {
 | 
			
		||||
	server *httptest.Server
 | 
			
		||||
	ds     *DockerSuite
 | 
			
		||||
	d      *Daemon
 | 
			
		||||
	ec     *graphEventsCounter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type graphEventsCounter struct {
 | 
			
		||||
	activations int
 | 
			
		||||
	creations   int
 | 
			
		||||
	removals    int
 | 
			
		||||
	gets        int
 | 
			
		||||
	puts        int
 | 
			
		||||
	stats       int
 | 
			
		||||
	cleanups    int
 | 
			
		||||
	exists      int
 | 
			
		||||
	init        int
 | 
			
		||||
	metadata    int
 | 
			
		||||
	diff        int
 | 
			
		||||
	applydiff   int
 | 
			
		||||
	changes     int
 | 
			
		||||
	diffsize    int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerExternalGraphdriverSuite) SetUpTest(c *check.C) {
 | 
			
		||||
	s.d = NewDaemon(c)
 | 
			
		||||
	s.ec = &graphEventsCounter{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerExternalGraphdriverSuite) TearDownTest(c *check.C) {
 | 
			
		||||
	s.d.Stop()
 | 
			
		||||
	s.ds.TearDownTest(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
 | 
			
		||||
	mux := http.NewServeMux()
 | 
			
		||||
	s.server = httptest.NewServer(mux)
 | 
			
		||||
 | 
			
		||||
	type graphDriverRequest struct {
 | 
			
		||||
		ID         string `json:",omitempty"`
 | 
			
		||||
		Parent     string `json:",omitempty"`
 | 
			
		||||
		MountLabel string `json:",omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type graphDriverResponse struct {
 | 
			
		||||
		Err      error             `json:",omitempty"`
 | 
			
		||||
		Dir      string            `json:",omitempty"`
 | 
			
		||||
		Exists   bool              `json:",omitempty"`
 | 
			
		||||
		Status   [][2]string       `json:",omitempty"`
 | 
			
		||||
		Metadata map[string]string `json:",omitempty"`
 | 
			
		||||
		Changes  []archive.Change  `json:",omitempty"`
 | 
			
		||||
		Size     int64             `json:",omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	respond := func(w http.ResponseWriter, data interface{}) {
 | 
			
		||||
		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
 | 
			
		||||
		switch t := data.(type) {
 | 
			
		||||
		case error:
 | 
			
		||||
			fmt.Fprintln(w, fmt.Sprintf(`{"Err": %s}`, t.Error()))
 | 
			
		||||
		case string:
 | 
			
		||||
			fmt.Fprintln(w, t)
 | 
			
		||||
		default:
 | 
			
		||||
			json.NewEncoder(w).Encode(&data)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	base, err := ioutil.TempDir("", "external-graph-test")
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
	vfsProto, err := vfs.Init(base, []string{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.Fatalf("error initializing graph driver: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	driver := graphdriver.NaiveDiffDriver(vfsProto)
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.activations++
 | 
			
		||||
		respond(w, `{"Implements": ["GraphDriver"]}`)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.init++
 | 
			
		||||
		respond(w, "{}")
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.creations++
 | 
			
		||||
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if err := driver.Create(req.ID, req.Parent); err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, "{}")
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.removals++
 | 
			
		||||
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := driver.Remove(req.ID); err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, "{}")
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.gets++
 | 
			
		||||
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dir, err := driver.Get(req.ID, req.MountLabel)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, &graphDriverResponse{Dir: dir})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Put", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.puts++
 | 
			
		||||
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := driver.Put(req.ID); err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, "{}")
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Exists", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.exists++
 | 
			
		||||
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, &graphDriverResponse{Exists: driver.Exists(req.ID)})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Status", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.stats++
 | 
			
		||||
		respond(w, `{"Status":{}}`)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.cleanups++
 | 
			
		||||
		err := driver.Cleanup()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, `{}`)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.GetMetadata", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.metadata++
 | 
			
		||||
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data, err := driver.GetMetadata(req.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, &graphDriverResponse{Metadata: data})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Diff", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.diff++
 | 
			
		||||
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diff, err := driver.Diff(req.ID, req.Parent)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		io.Copy(w, diff)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.Changes", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.changes++
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		changes, err := driver.Changes(req.ID, req.Parent)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, &graphDriverResponse{Changes: changes})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.applydiff++
 | 
			
		||||
		id := r.URL.Query().Get("id")
 | 
			
		||||
		parent := r.URL.Query().Get("parent")
 | 
			
		||||
 | 
			
		||||
		size, err := driver.ApplyDiff(id, parent, r.Body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, &graphDriverResponse{Size: size})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("/GraphDriver.DiffSize", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		s.ec.diffsize++
 | 
			
		||||
 | 
			
		||||
		var req graphDriverRequest
 | 
			
		||||
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), 500)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		size, err := driver.DiffSize(req.ID, req.Parent)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			respond(w, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		respond(w, &graphDriverResponse{Size: size})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
 | 
			
		||||
		c.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ioutil.WriteFile("/etc/docker/plugins/test-external-graph-driver.spec", []byte(s.server.URL), 0644); err != nil {
 | 
			
		||||
		c.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerExternalGraphdriverSuite) TearDownSuite(c *check.C) {
 | 
			
		||||
	s.server.Close()
 | 
			
		||||
 | 
			
		||||
	if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
 | 
			
		||||
		c.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) {
 | 
			
		||||
	c.Assert(s.d.StartWithBusybox("-s", "test-external-graph-driver"), check.IsNil)
 | 
			
		||||
 | 
			
		||||
	out, err := s.d.Cmd("run", "-d", "--name=graphtest", "busybox", "sh", "-c", "echo hello > /hello")
 | 
			
		||||
	c.Assert(err, check.IsNil, check.Commentf(out))
 | 
			
		||||
 | 
			
		||||
	err = s.d.Restart("-s", "test-external-graph-driver")
 | 
			
		||||
 | 
			
		||||
	out, err = s.d.Cmd("inspect", "--format='{{.GraphDriver.Name}}'", "graphtest")
 | 
			
		||||
	c.Assert(err, check.IsNil, check.Commentf(out))
 | 
			
		||||
	c.Assert(strings.TrimSpace(out), check.Equals, "test-external-graph-driver")
 | 
			
		||||
 | 
			
		||||
	out, err = s.d.Cmd("diff", "graphtest")
 | 
			
		||||
	c.Assert(err, check.IsNil, check.Commentf(out))
 | 
			
		||||
	c.Assert(strings.Contains(out, "A /hello"), check.Equals, true)
 | 
			
		||||
 | 
			
		||||
	out, err = s.d.Cmd("rm", "-f", "graphtest")
 | 
			
		||||
	c.Assert(err, check.IsNil, check.Commentf(out))
 | 
			
		||||
 | 
			
		||||
	out, err = s.d.Cmd("info")
 | 
			
		||||
	c.Assert(err, check.IsNil, check.Commentf(out))
 | 
			
		||||
 | 
			
		||||
	err = s.d.Stop()
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
 | 
			
		||||
	c.Assert(s.ec.activations, check.Equals, 2)
 | 
			
		||||
	c.Assert(s.ec.init, check.Equals, 2)
 | 
			
		||||
	c.Assert(s.ec.creations >= 1, check.Equals, true)
 | 
			
		||||
	c.Assert(s.ec.removals >= 1, check.Equals, true)
 | 
			
		||||
	c.Assert(s.ec.gets >= 1, check.Equals, true)
 | 
			
		||||
	c.Assert(s.ec.puts >= 1, check.Equals, true)
 | 
			
		||||
	c.Assert(s.ec.stats, check.Equals, 1)
 | 
			
		||||
	c.Assert(s.ec.cleanups, check.Equals, 2)
 | 
			
		||||
	c.Assert(s.ec.exists >= 1, check.Equals, true)
 | 
			
		||||
	c.Assert(s.ec.applydiff >= 1, check.Equals, true)
 | 
			
		||||
	c.Assert(s.ec.changes, check.Equals, 1)
 | 
			
		||||
	c.Assert(s.ec.diffsize, check.Equals, 0)
 | 
			
		||||
	c.Assert(s.ec.diff, check.Equals, 0)
 | 
			
		||||
	c.Assert(s.ec.metadata, check.Equals, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriverPull(c *check.C) {
 | 
			
		||||
	testRequires(c, Network)
 | 
			
		||||
	c.Assert(s.d.Start(), check.IsNil)
 | 
			
		||||
 | 
			
		||||
	out, err := s.d.Cmd("pull", "busybox:latest")
 | 
			
		||||
	c.Assert(err, check.IsNil, check.Commentf(out))
 | 
			
		||||
 | 
			
		||||
	out, err = s.d.Cmd("run", "-d", "busybox", "top")
 | 
			
		||||
	c.Assert(err, check.IsNil, check.Commentf(out))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -68,11 +68,8 @@ func init() {
 | 
			
		|||
	// Similarly, it will be perfectly valid to also run CLI tests from
 | 
			
		||||
	// a Linux CLI (built with the daemon tag) against a Windows daemon.
 | 
			
		||||
	if len(os.Getenv("DOCKER_REMOTE_DAEMON")) > 0 {
 | 
			
		||||
		fmt.Println("INFO: Testing against a remote daemon")
 | 
			
		||||
		isLocalDaemon = false
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("INFO: Testing against a local daemon")
 | 
			
		||||
		isLocalDaemon = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import (
 | 
			
		|||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
| 
						 | 
				
			
			@ -52,19 +53,41 @@ type Client struct {
 | 
			
		|||
// Call calls the specified method with the specified arguments for the plugin.
 | 
			
		||||
// It will retry for 30 seconds if a failure occurs when calling.
 | 
			
		||||
func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
 | 
			
		||||
	return c.callWithRetry(serviceMethod, args, ret, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret interface{}, retry bool) error {
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	if err := json.NewEncoder(&buf).Encode(args); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req, err := http.NewRequest("POST", "/"+serviceMethod, &buf)
 | 
			
		||||
	body, err := c.callWithRetry(serviceMethod, &buf, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer body.Close()
 | 
			
		||||
	return json.NewDecoder(body).Decode(&ret)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Stream calls the specified method with the specified arguments for the plugin and returns the response body
 | 
			
		||||
func (c *Client) Stream(serviceMethod string, args interface{}) (io.ReadCloser, error) {
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	if err := json.NewEncoder(&buf).Encode(args); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return c.callWithRetry(serviceMethod, &buf, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendFile calls the specified method, and passes through the IO stream
 | 
			
		||||
func (c *Client) SendFile(serviceMethod string, data io.Reader, ret interface{}) error {
 | 
			
		||||
	body, err := c.callWithRetry(serviceMethod, data, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return json.NewDecoder(body).Decode(&ret)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) (io.ReadCloser, error) {
 | 
			
		||||
	req, err := http.NewRequest("POST", "/"+serviceMethod, data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Add("Accept", versionMimetype)
 | 
			
		||||
	req.URL.Scheme = "http"
 | 
			
		||||
	req.URL.Host = c.addr
 | 
			
		||||
| 
						 | 
				
			
			@ -76,12 +99,12 @@ func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret inter
 | 
			
		|||
		resp, err := c.http.Do(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if !retry {
 | 
			
		||||
				return err
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			timeOff := backoff(retries)
 | 
			
		||||
			if abort(start, timeOff) {
 | 
			
		||||
				return err
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			retries++
 | 
			
		||||
			logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
 | 
			
		||||
| 
						 | 
				
			
			@ -89,16 +112,14 @@ func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret inter
 | 
			
		|||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		defer resp.Body.Close()
 | 
			
		||||
		if resp.StatusCode != http.StatusOK {
 | 
			
		||||
			remoteErr, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return &remoteError{err.Error(), serviceMethod}
 | 
			
		||||
				return nil, &remoteError{err.Error(), serviceMethod}
 | 
			
		||||
			}
 | 
			
		||||
			return &remoteError{string(remoteErr), serviceMethod}
 | 
			
		||||
			return nil, &remoteError{string(remoteErr), serviceMethod}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return json.NewDecoder(resp.Body).Decode(&ret)
 | 
			
		||||
		return resp.Body, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ func teardownRemotePluginServer() {
 | 
			
		|||
 | 
			
		||||
func TestFailedConnection(t *testing.T) {
 | 
			
		||||
	c, _ := NewClient("tcp://127.0.0.1:1", tlsconfig.Options{InsecureSkipVerify: true})
 | 
			
		||||
	err := c.callWithRetry("Service.Method", nil, nil, false)
 | 
			
		||||
	_, err := c.callWithRetry("Service.Method", nil, false)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("Unexpected successful connection")
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue