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 {
|
if initFunc, exists := drivers[name]; exists {
|
||||||
return initFunc(filepath.Join(home, name), options)
|
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)
|
logrus.Errorf("Failed to GetDriver graph %s %s", name, home)
|
||||||
return nil, ErrNotSupported
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/reexec"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
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)
|
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
|
// Similarly, it will be perfectly valid to also run CLI tests from
|
||||||
// a Linux CLI (built with the daemon tag) against a Windows daemon.
|
// a Linux CLI (built with the daemon tag) against a Windows daemon.
|
||||||
if len(os.Getenv("DOCKER_REMOTE_DAEMON")) > 0 {
|
if len(os.Getenv("DOCKER_REMOTE_DAEMON")) > 0 {
|
||||||
fmt.Println("INFO: Testing against a remote daemon")
|
|
||||||
isLocalDaemon = false
|
isLocalDaemon = false
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("INFO: Testing against a local daemon")
|
|
||||||
isLocalDaemon = true
|
isLocalDaemon = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -52,19 +53,41 @@ type Client struct {
|
||||||
// Call calls the specified method with the specified arguments for the plugin.
|
// Call calls the specified method with the specified arguments for the plugin.
|
||||||
// It will retry for 30 seconds if a failure occurs when calling.
|
// It will retry for 30 seconds if a failure occurs when calling.
|
||||||
func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
|
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
|
var buf bytes.Buffer
|
||||||
if err := json.NewEncoder(&buf).Encode(args); err != nil {
|
if err := json.NewEncoder(&buf).Encode(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
body, err := c.callWithRetry(serviceMethod, &buf, true)
|
||||||
req, err := http.NewRequest("POST", "/"+serviceMethod, &buf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.Header.Add("Accept", versionMimetype)
|
||||||
req.URL.Scheme = "http"
|
req.URL.Scheme = "http"
|
||||||
req.URL.Host = c.addr
|
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)
|
resp, err := c.http.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !retry {
|
if !retry {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
timeOff := backoff(retries)
|
timeOff := backoff(retries)
|
||||||
if abort(start, timeOff) {
|
if abort(start, timeOff) {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
retries++
|
retries++
|
||||||
logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
remoteErr, err := ioutil.ReadAll(resp.Body)
|
remoteErr, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
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 resp.Body, nil
|
||||||
return json.NewDecoder(resp.Body).Decode(&ret)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ func teardownRemotePluginServer() {
|
||||||
|
|
||||||
func TestFailedConnection(t *testing.T) {
|
func TestFailedConnection(t *testing.T) {
|
||||||
c, _ := NewClient("tcp://127.0.0.1:1", tlsconfig.Options{InsecureSkipVerify: true})
|
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 {
|
if err == nil {
|
||||||
t.Fatal("Unexpected successful connection")
|
t.Fatal("Unexpected successful connection")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue