diff --git a/api/mockclient/mock.go b/api/mockclient/mock.go new file mode 100644 index 0000000000..b63186e26a --- /dev/null +++ b/api/mockclient/mock.go @@ -0,0 +1,372 @@ +package mockclient + +import ( + "io" + + "github.com/docker/engine-api/client" + "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/container" + "github.com/docker/engine-api/types/filters" + "github.com/docker/engine-api/types/network" + "github.com/docker/engine-api/types/registry" + "github.com/stretchr/testify/mock" + "golang.org/x/net/context" +) + +// MockClient is a mock API Client based on engine-api +type MockClient struct { + mock.Mock +} + +// NewMockClient creates a new mock client +func NewMockClient() *MockClient { + return &MockClient{} +} + +// ClientVersion returns the version string associated with this instance of the Client +func (client *MockClient) ClientVersion() string { + args := client.Mock.Called() + return args.String(0) +} + +// ContainerAttach attaches a connection to a container in the server +func (client *MockClient) ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error) { + args := client.Mock.Called(options) + return args.Get(0).(types.HijackedResponse), args.Error(1) +} + +// ContainerCommit applies changes into a container and creates a new tagged image +func (client *MockClient) ContainerCommit(options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) { + args := client.Mock.Called(options) + return args.Get(0).(types.ContainerCommitResponse), args.Error(1) +} + +// ContainerCreate creates a new container based in the given configuration +func (client *MockClient) ContainerCreate(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) { + args := client.Mock.Called(config, hostConfig, networkingConfig, containerName) + return args.Get(0).(types.ContainerCreateResponse), args.Error(1) +} + +// ContainerDiff shows differences in a container filesystem since it was started +func (client *MockClient) ContainerDiff(containerID string) ([]types.ContainerChange, error) { + args := client.Mock.Called(containerID) + return args.Get(0).([]types.ContainerChange), args.Error(1) +} + +// ContainerExecAttach attaches a connection to an exec process in the server +func (client *MockClient) ContainerExecAttach(execID string, config types.ExecConfig) (types.HijackedResponse, error) { + args := client.Mock.Called(execID, config) + return args.Get(0).(types.HijackedResponse), args.Error(1) +} + +// ContainerExecCreate creates a new exec configuration to run an exec process +func (client *MockClient) ContainerExecCreate(config types.ExecConfig) (types.ContainerExecCreateResponse, error) { + args := client.Mock.Called(config) + return args.Get(0).(types.ContainerExecCreateResponse), args.Error(1) +} + +// ContainerExecInspect returns information about a specific exec process on the docker host +func (client *MockClient) ContainerExecInspect(execID string) (types.ContainerExecInspect, error) { + args := client.Mock.Called(execID) + return args.Get(0).(types.ContainerExecInspect), args.Error(1) +} + +// ContainerExecResize changes the size of the tty for an exec process running inside a container +func (client *MockClient) ContainerExecResize(options types.ResizeOptions) error { + args := client.Mock.Called(options) + return args.Error(0) +} + +// ContainerExecStart starts an exec process already create in the docker host +func (client *MockClient) ContainerExecStart(execID string, config types.ExecStartCheck) error { + args := client.Mock.Called(execID, config) + return args.Error(0) +} + +// ContainerExport retrieves the raw contents of a container and returns them as an io.ReadCloser +func (client *MockClient) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, containerID) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ContainerInspect returns the container information +func (client *MockClient) ContainerInspect(containerID string) (types.ContainerJSON, error) { + args := client.Mock.Called(containerID) + return args.Get(0).(types.ContainerJSON), args.Error(1) +} + +// ContainerInspectWithRaw returns the container information and its raw representation +func (client *MockClient) ContainerInspectWithRaw(containerID string, getSize bool) (types.ContainerJSON, []byte, error) { + args := client.Mock.Called(containerID, getSize) + return args.Get(0).(types.ContainerJSON), args.Get(1).([]byte), args.Error(2) +} + +// ContainerKill terminates the container process but does not remove the container from the docker host +func (client *MockClient) ContainerKill(containerID, signal string) error { + args := client.Mock.Called(containerID, signal) + return args.Error(0) +} + +// ContainerList returns the list of containers in the docker host +func (client *MockClient) ContainerList(options types.ContainerListOptions) ([]types.Container, error) { + args := client.Mock.Called(options) + return args.Get(0).([]types.Container), args.Error(1) +} + +// ContainerLogs returns the logs generated by a container in an io.ReadCloser +func (client *MockClient) ContainerLogs(ctx context.Context, options types.ContainerLogsOptions) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, options) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ContainerPause pauses the main process of a given container without terminating it +func (client *MockClient) ContainerPause(containerID string) error { + args := client.Mock.Called(containerID) + return args.Error(0) +} + +// ContainerRemove kills and removes a container from the docker host +func (client *MockClient) ContainerRemove(options types.ContainerRemoveOptions) error { + args := client.Mock.Called(options) + return args.Error(0) +} + +// ContainerRename changes the name of a given container +func (client *MockClient) ContainerRename(containerID, newContainerName string) error { + args := client.Mock.Called(containerID, newContainerName) + return args.Error(0) +} + +// ContainerResize changes the size of the tty for a container +func (client *MockClient) ContainerResize(options types.ResizeOptions) error { + args := client.Mock.Called(options) + return args.Error(0) +} + +// ContainerRestart stops and starts a container again +func (client *MockClient) ContainerRestart(containerID string, timeout int) error { + args := client.Mock.Called(containerID, timeout) + return args.Error(0) +} + +// ContainerStatPath returns Stat information about a path inside the container filesystem +func (client *MockClient) ContainerStatPath(containerID, path string) (types.ContainerPathStat, error) { + args := client.Mock.Called(containerID, path) + return args.Get(0).(types.ContainerPathStat), args.Error(1) +} + +// ContainerStats returns near realtime stats for a given container +func (client *MockClient) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, containerID, stream) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ContainerStart sends a request to the docker daemon to start a container +func (client *MockClient) ContainerStart(containerID string) error { + args := client.Mock.Called(containerID) + return args.Error(0) +} + +// ContainerStop stops a container without terminating the process +func (client *MockClient) ContainerStop(containerID string, timeout int) error { + args := client.Mock.Called(containerID, timeout) + return args.Error(0) +} + +// ContainerTop shows process information from within a container +func (client *MockClient) ContainerTop(containerID string, arguments []string) (types.ContainerProcessList, error) { + args := client.Mock.Called(containerID, arguments) + return args.Get(0).(types.ContainerProcessList), args.Error(1) +} + +// ContainerUnpause resumes the process execution within a container +func (client *MockClient) ContainerUnpause(containerID string) error { + args := client.Mock.Called(containerID) + return args.Error(0) +} + +// ContainerUpdate updates resources of a container +func (client *MockClient) ContainerUpdate(containerID string, updateConfig container.UpdateConfig) error { + args := client.Mock.Called(containerID, updateConfig) + return args.Error(0) +} + +// ContainerWait pauses execution until a container exits +func (client *MockClient) ContainerWait(ctx context.Context, containerID string) (int, error) { + args := client.Mock.Called(ctx, containerID) + return args.Int(0), args.Error(1) +} + +// CopyFromContainer gets the content from the container and returns it as a Reader to manipulate it in the host +func (client *MockClient) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { + args := client.Mock.Called(ctx, containerID, srcPath) + return args.Get(0).(io.ReadCloser), args.Get(1).(types.ContainerPathStat), args.Error(2) +} + +// CopyToContainer copies content into the container filesystem +func (client *MockClient) CopyToContainer(ctx context.Context, options types.CopyToContainerOptions) error { + args := client.Mock.Called(ctx, options) + return args.Error(0) +} + +// Events returns a stream of events in the daemon in a ReadCloser +func (client *MockClient) Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, options) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ImageBuild sends request to the daemon to build images +func (client *MockClient) ImageBuild(ctx context.Context, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { + args := client.Mock.Called(ctx, options) + return args.Get(0).(types.ImageBuildResponse), args.Error(1) +} + +// ImageCreate creates a new image based in the parent options +func (client *MockClient) ImageCreate(ctx context.Context, options types.ImageCreateOptions) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, options) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ImageHistory returns the changes in an image in history format +func (client *MockClient) ImageHistory(imageID string) ([]types.ImageHistory, error) { + args := client.Mock.Called(imageID) + return args.Get(0).([]types.ImageHistory), args.Error(1) +} + +// ImageImport creates a new image based in the source options +func (client *MockClient) ImageImport(ctx context.Context, options types.ImageImportOptions) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, options) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ImageInspectWithRaw returns the image information and it's raw representation +func (client *MockClient) ImageInspectWithRaw(imageID string, getSize bool) (types.ImageInspect, []byte, error) { + args := client.Mock.Called(imageID, getSize) + return args.Get(0).(types.ImageInspect), args.Get(1).([]byte), args.Error(2) +} + +// ImageList returns a list of images in the docker host +func (client *MockClient) ImageList(options types.ImageListOptions) ([]types.Image, error) { + args := client.Mock.Called(options) + return args.Get(0).([]types.Image), args.Error(1) +} + +// ImageLoad loads an image in the docker host from the client host +func (client *MockClient) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) { + args := client.Mock.Called(ctx, input, quiet) + return args.Get(0).(types.ImageLoadResponse), args.Error(1) +} + +// ImagePull requests the docker host to pull an image from a remote registry +func (client *MockClient) ImagePull(ctx context.Context, options types.ImagePullOptions, privilegeFunc client.RequestPrivilegeFunc) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, options, privilegeFunc) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ImagePush requests the docker host to push an image to a remote registry +func (client *MockClient) ImagePush(ctx context.Context, options types.ImagePushOptions, privilegeFunc client.RequestPrivilegeFunc) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, options, privilegeFunc) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ImageRemove removes an image from the docker host +func (client *MockClient) ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error) { + args := client.Mock.Called(options) + return args.Get(0).([]types.ImageDelete), args.Error(1) +} + +// ImageSearch makes the docker host to search by a term in a remote registry +func (client *MockClient) ImageSearch(options types.ImageSearchOptions, privilegeFunc client.RequestPrivilegeFunc) ([]registry.SearchResult, error) { + args := client.Mock.Called(options, privilegeFunc) + return args.Get(0).([]registry.SearchResult), args.Error(1) +} + +// ImageSave retrieves one or more images from the docker host as an io.ReadCloser +func (client *MockClient) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { + args := client.Mock.Called(ctx, imageIDs) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +// ImageTag tags an image in the docker host +func (client *MockClient) ImageTag(options types.ImageTagOptions) error { + args := client.Mock.Called(options) + return args.Error(0) +} + +// Info returns information about the docker server +func (client *MockClient) Info() (types.Info, error) { + args := client.Mock.Called() + return args.Get(0).(types.Info), args.Error(1) +} + +// NetworkConnect connects a container to an existent network in the docker host +func (client *MockClient) NetworkConnect(networkID, containerID string, config *network.EndpointSettings) error { + args := client.Mock.Called(networkID, containerID, config) + return args.Error(0) +} + +// NetworkCreate creates a new network in the docker host +func (client *MockClient) NetworkCreate(options types.NetworkCreate) (types.NetworkCreateResponse, error) { + args := client.Mock.Called(options) + return args.Get(0).(types.NetworkCreateResponse), args.Error(1) +} + +// NetworkDisconnect disconnects a container from an existent network in the docker host +func (client *MockClient) NetworkDisconnect(networkID, containerID string, force bool) error { + args := client.Mock.Called(networkID, containerID, force) + return args.Error(0) +} + +// NetworkInspect returns the information for a specific network configured in the docker host +func (client *MockClient) NetworkInspect(networkID string) (types.NetworkResource, error) { + args := client.Mock.Called(networkID) + return args.Get(0).(types.NetworkResource), args.Error(1) +} + +// NetworkList returns the list of networks configured in the docker host +func (client *MockClient) NetworkList(options types.NetworkListOptions) ([]types.NetworkResource, error) { + args := client.Mock.Called(options) + return args.Get(0).([]types.NetworkResource), args.Error(1) +} + +// NetworkRemove removes an existent network from the docker host +func (client *MockClient) NetworkRemove(networkID string) error { + args := client.Mock.Called(networkID) + return args.Error(0) +} + +// RegistryLogin authenticates the docker server with a given docker registry +func (client *MockClient) RegistryLogin(auth types.AuthConfig) (types.AuthResponse, error) { + args := client.Mock.Called(auth) + return args.Get(0).(types.AuthResponse), args.Error(1) +} + +// ServerVersion returns information of the docker client and server host +func (client *MockClient) ServerVersion() (types.Version, error) { + args := client.Mock.Called() + return args.Get(0).(types.Version), args.Error(1) +} + +// VolumeCreate creates a volume in the docker host +func (client *MockClient) VolumeCreate(options types.VolumeCreateRequest) (types.Volume, error) { + args := client.Mock.Called(options) + return args.Get(0).(types.Volume), args.Error(1) +} + +// VolumeInspect returns the information about a specific volume in the docker host +func (client *MockClient) VolumeInspect(volumeID string) (types.Volume, error) { + args := client.Mock.Called(volumeID) + return args.Get(0).(types.Volume), args.Error(1) +} + +// VolumeList returns the volumes configured in the docker host +func (client *MockClient) VolumeList(filter filters.Args) (types.VolumesListResponse, error) { + args := client.Mock.Called(filter) + return args.Get(0).(types.VolumesListResponse), args.Error(1) +} + +// VolumeRemove removes a volume from the docker host +func (client *MockClient) VolumeRemove(volumeID string) error { + args := client.Mock.Called(volumeID) + return args.Error(0) +} diff --git a/api/mockclient/mock_test.go b/api/mockclient/mock_test.go new file mode 100644 index 0000000000..17f10a12a6 --- /dev/null +++ b/api/mockclient/mock_test.go @@ -0,0 +1,33 @@ +package mockclient + +import ( + "reflect" + "testing" + + "github.com/docker/engine-api/client" + "github.com/docker/engine-api/types" +) + +func TestMock(t *testing.T) { + mock := NewMockClient() + mock.On("ServerVersion").Return(types.Version{Version: "foo"}, nil).Once() + + v, err := mock.ServerVersion() + if err != nil { + t.Fatal(err) + } + if v.Version != "foo" { + t.Fatal(v) + } + + mock.Mock.AssertExpectations(t) +} + +func TestMockInterface(t *testing.T) { + iface := reflect.TypeOf((*client.APIClient)(nil)).Elem() + mock := NewMockClient() + + if !reflect.TypeOf(mock).Implements(iface) { + t.Fatalf("Mock does not implement the APIClient interface") + } +} diff --git a/api/nopclient/nop.go b/api/nopclient/nop.go new file mode 100644 index 0000000000..cb42ee301e --- /dev/null +++ b/api/nopclient/nop.go @@ -0,0 +1,318 @@ +package nopclient + +import ( + "errors" + "io" + + "golang.org/x/net/context" + + "github.com/docker/engine-api/client" + "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/container" + "github.com/docker/engine-api/types/filters" + "github.com/docker/engine-api/types/network" + "github.com/docker/engine-api/types/registry" +) + +var ( + errNoEngine = errors.New("Engine no longer exists") +) + +// NopClient is a nop API Client based on engine-api +type NopClient struct { +} + +// NewNopClient creates a new nop client +func NewNopClient() *NopClient { + return &NopClient{} +} + +// ClientVersion returns the version string associated with this instance of the Client +func (client *NopClient) ClientVersion() string { + return "" +} + +// ContainerAttach attaches a connection to a container in the server +func (client *NopClient) ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error) { + return types.HijackedResponse{}, errNoEngine +} + +// ContainerCommit applies changes into a container and creates a new tagged image +func (client *NopClient) ContainerCommit(options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) { + return types.ContainerCommitResponse{}, errNoEngine +} + +// ContainerCreate creates a new container based in the given configuration +func (client *NopClient) ContainerCreate(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) { + return types.ContainerCreateResponse{}, errNoEngine +} + +// ContainerDiff shows differences in a container filesystem since it was started +func (client *NopClient) ContainerDiff(containerID string) ([]types.ContainerChange, error) { + return nil, errNoEngine +} + +// ContainerExecAttach attaches a connection to an exec process in the server +func (client *NopClient) ContainerExecAttach(execID string, config types.ExecConfig) (types.HijackedResponse, error) { + return types.HijackedResponse{}, errNoEngine +} + +// ContainerExecCreate creates a new exec configuration to run an exec process +func (client *NopClient) ContainerExecCreate(config types.ExecConfig) (types.ContainerExecCreateResponse, error) { + return types.ContainerExecCreateResponse{}, errNoEngine +} + +// ContainerExecInspect returns information about a specific exec process on the docker host +func (client *NopClient) ContainerExecInspect(execID string) (types.ContainerExecInspect, error) { + return types.ContainerExecInspect{}, errNoEngine +} + +// ContainerExecResize changes the size of the tty for an exec process running inside a container +func (client *NopClient) ContainerExecResize(options types.ResizeOptions) error { + return errNoEngine +} + +// ContainerExecStart starts an exec process already create in the docker host +func (client *NopClient) ContainerExecStart(execID string, config types.ExecStartCheck) error { + return errNoEngine +} + +// ContainerExport retrieves the raw contents of a container and returns them as an io.ReadCloser +func (client *NopClient) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ContainerInspect returns the container information +func (client *NopClient) ContainerInspect(containerID string) (types.ContainerJSON, error) { + return types.ContainerJSON{}, errNoEngine +} + +// ContainerInspectWithRaw returns the container information and its raw representation +func (client *NopClient) ContainerInspectWithRaw(containerID string, getSize bool) (types.ContainerJSON, []byte, error) { + return types.ContainerJSON{}, nil, errNoEngine +} + +// ContainerKill terminates the container process but does not remove the container from the docker host +func (client *NopClient) ContainerKill(containerID, signal string) error { + return errNoEngine +} + +// ContainerList returns the list of containers in the docker host +func (client *NopClient) ContainerList(options types.ContainerListOptions) ([]types.Container, error) { + return nil, errNoEngine +} + +// ContainerLogs returns the logs generated by a container in an io.ReadCloser +func (client *NopClient) ContainerLogs(ctx context.Context, options types.ContainerLogsOptions) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ContainerPause pauses the main process of a given container without terminating it +func (client *NopClient) ContainerPause(containerID string) error { + return errNoEngine +} + +// ContainerRemove kills and removes a container from the docker host +func (client *NopClient) ContainerRemove(options types.ContainerRemoveOptions) error { + return errNoEngine +} + +// ContainerRename changes the name of a given container +func (client *NopClient) ContainerRename(containerID, newContainerName string) error { + return errNoEngine +} + +// ContainerResize changes the size of the tty for a container +func (client *NopClient) ContainerResize(options types.ResizeOptions) error { + return errNoEngine +} + +// ContainerRestart stops and starts a container again +func (client *NopClient) ContainerRestart(containerID string, timeout int) error { + return errNoEngine +} + +// ContainerStatPath returns Stat information about a path inside the container filesystem +func (client *NopClient) ContainerStatPath(containerID, path string) (types.ContainerPathStat, error) { + return types.ContainerPathStat{}, errNoEngine +} + +// ContainerStats returns near realtime stats for a given container +func (client *NopClient) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ContainerStart sends a request to the docker daemon to start a container +func (client *NopClient) ContainerStart(containerID string) error { + return errNoEngine +} + +// ContainerStop stops a container without terminating the process +func (client *NopClient) ContainerStop(containerID string, timeout int) error { + return errNoEngine +} + +// ContainerTop shows process information from within a container +func (client *NopClient) ContainerTop(containerID string, arguments []string) (types.ContainerProcessList, error) { + return types.ContainerProcessList{}, errNoEngine +} + +// ContainerUnpause resumes the process execution within a container +func (client *NopClient) ContainerUnpause(containerID string) error { + return errNoEngine +} + +// ContainerUpdate updates resources of a container +func (client *NopClient) ContainerUpdate(containerID string, updateConfig container.UpdateConfig) error { + return errNoEngine +} + +// ContainerWait pauses execution until a container exits +func (client *NopClient) ContainerWait(ctx context.Context, containerID string) (int, error) { + return 0, errNoEngine +} + +// CopyFromContainer gets the content from the container and returns it as a Reader to manipulate it in the host +func (client *NopClient) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { + return nil, types.ContainerPathStat{}, errNoEngine +} + +// CopyToContainer copies content into the container filesystem +func (client *NopClient) CopyToContainer(ctx context.Context, options types.CopyToContainerOptions) error { + return errNoEngine +} + +// Events returns a stream of events in the daemon in a ReadCloser +func (client *NopClient) Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ImageBuild sends request to the daemon to build images +func (client *NopClient) ImageBuild(ctx context.Context, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { + return types.ImageBuildResponse{}, errNoEngine +} + +// ImageCreate creates a new image based in the parent options +func (client *NopClient) ImageCreate(ctx context.Context, options types.ImageCreateOptions) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ImageHistory returns the changes in an image in history format +func (client *NopClient) ImageHistory(imageID string) ([]types.ImageHistory, error) { + return nil, errNoEngine +} + +// ImageImport creates a new image based in the source options +func (client *NopClient) ImageImport(ctx context.Context, options types.ImageImportOptions) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ImageInspectWithRaw returns the image information and it's raw representation +func (client *NopClient) ImageInspectWithRaw(imageID string, getSize bool) (types.ImageInspect, []byte, error) { + return types.ImageInspect{}, nil, errNoEngine +} + +// ImageList returns a list of images in the docker host +func (client *NopClient) ImageList(options types.ImageListOptions) ([]types.Image, error) { + return nil, errNoEngine +} + +// ImageLoad loads an image in the docker host from the client host +func (client *NopClient) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) { + return types.ImageLoadResponse{}, errNoEngine +} + +// ImagePull requests the docker host to pull an image from a remote registry +func (client *NopClient) ImagePull(ctx context.Context, options types.ImagePullOptions, privilegeFunc client.RequestPrivilegeFunc) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ImagePush requests the docker host to push an image to a remote registry +func (client *NopClient) ImagePush(ctx context.Context, options types.ImagePushOptions, privilegeFunc client.RequestPrivilegeFunc) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ImageRemove removes an image from the docker host +func (client *NopClient) ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error) { + return nil, errNoEngine +} + +// ImageSearch makes the docker host to search by a term in a remote registry +func (client *NopClient) ImageSearch(options types.ImageSearchOptions, privilegeFunc client.RequestPrivilegeFunc) ([]registry.SearchResult, error) { + return nil, errNoEngine +} + +// ImageSave retrieves one or more images from the docker host as an io.ReadCloser +func (client *NopClient) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { + return nil, errNoEngine +} + +// ImageTag tags an image in the docker host +func (client *NopClient) ImageTag(options types.ImageTagOptions) error { + return errNoEngine +} + +// Info returns information about the docker server +func (client *NopClient) Info() (types.Info, error) { + return types.Info{}, errNoEngine +} + +// NetworkConnect connects a container to an existent network in the docker host +func (client *NopClient) NetworkConnect(networkID, containerID string, config *network.EndpointSettings) error { + return errNoEngine +} + +// NetworkCreate creates a new network in the docker host +func (client *NopClient) NetworkCreate(options types.NetworkCreate) (types.NetworkCreateResponse, error) { + return types.NetworkCreateResponse{}, errNoEngine +} + +// NetworkDisconnect disconnects a container from an existent network in the docker host +func (client *NopClient) NetworkDisconnect(networkID, containerID string, force bool) error { + return errNoEngine +} + +// NetworkInspect returns the information for a specific network configured in the docker host +func (client *NopClient) NetworkInspect(networkID string) (types.NetworkResource, error) { + return types.NetworkResource{}, errNoEngine +} + +// NetworkList returns the list of networks configured in the docker host +func (client *NopClient) NetworkList(options types.NetworkListOptions) ([]types.NetworkResource, error) { + return nil, errNoEngine +} + +// NetworkRemove removes an existent network from the docker host +func (client *NopClient) NetworkRemove(networkID string) error { + return errNoEngine +} + +// RegistryLogin authenticates the docker server with a given docker registry +func (client *NopClient) RegistryLogin(auth types.AuthConfig) (types.AuthResponse, error) { + return types.AuthResponse{}, errNoEngine +} + +// ServerVersion returns information of the docker client and server host +func (client *NopClient) ServerVersion() (types.Version, error) { + return types.Version{}, errNoEngine +} + +// VolumeCreate creates a volume in the docker host +func (client *NopClient) VolumeCreate(options types.VolumeCreateRequest) (types.Volume, error) { + return types.Volume{}, errNoEngine +} + +// VolumeInspect returns the information about a specific volume in the docker host +func (client *NopClient) VolumeInspect(volumeID string) (types.Volume, error) { + return types.Volume{}, errNoEngine +} + +// VolumeList returns the volumes configured in the docker host +func (client *NopClient) VolumeList(filter filters.Args) (types.VolumesListResponse, error) { + return types.VolumesListResponse{}, errNoEngine +} + +// VolumeRemove removes a volume from the docker host +func (client *NopClient) VolumeRemove(volumeID string) error { + return errNoEngine +} diff --git a/api/nopclient/nop_test.go b/api/nopclient/nop_test.go new file mode 100644 index 0000000000..7cbf9925b9 --- /dev/null +++ b/api/nopclient/nop_test.go @@ -0,0 +1,25 @@ +package nopclient + +import ( + "reflect" + "testing" + + "github.com/docker/engine-api/client" +) + +func TestNop(t *testing.T) { + nop := NewNopClient() + _, err := nop.Info() + if err != errNoEngine { + t.Fatalf("Nop client did not return error") + } +} + +func TestNopInterface(t *testing.T) { + iface := reflect.TypeOf((*client.APIClient)(nil)).Elem() + nop := NewNopClient() + + if !reflect.TypeOf(nop).Implements(iface) { + t.Fatalf("Nop does not implement the APIClient interface") + } +} diff --git a/cluster/engine.go b/cluster/engine.go index 01a14a7882..0f6809084f 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -16,6 +16,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/version" engineapi "github.com/docker/engine-api/client" + engineapinop "github.com/docker/swarm/api/nopclient" "github.com/samalba/dockerclient" "github.com/samalba/dockerclient/nopclient" ) @@ -125,6 +126,7 @@ func NewEngine(addr string, overcommitRatio float64, opts *EngineOpts) *Engine { e := &Engine{ Addr: addr, client: nopclient.NewNopClient(), + apiClient: engineapinop.NewNopClient(), refreshDelayer: newDelayer(opts.RefreshMinInterval, opts.RefreshMaxInterval), Labels: make(map[string]string), stopCh: make(chan struct{}), @@ -165,7 +167,13 @@ func (e *Engine) Connect(config *tls.Config) error { if err != nil { return err } - return e.ConnectWithClient(c) + // Use HTTP Client used by dockerclient to create engine-api client + apiClient, err := engineapi.NewClient("tcp://"+e.Addr, "", c.HTTPClient, nil) + if err != nil { + return err + } + + return e.ConnectWithClient(c, apiClient) } // StartMonitorEvents monitors events from the engine @@ -186,15 +194,8 @@ func (e *Engine) StartMonitorEvents() { } // ConnectWithClient is exported -func (e *Engine) ConnectWithClient(client dockerclient.Client) error { +func (e *Engine) ConnectWithClient(client dockerclient.Client, apiClient engineapi.APIClient) error { e.client = client - - // Use HTTP Client used by dockerclient to create engine-api client - httpClient, _ := e.HTTPClientAndScheme() - apiClient, err := engineapi.NewClient("tcp://"+e.Addr, "", httpClient, nil) - if err != nil { - return err - } e.apiClient = apiClient // Fetch the engine labels. @@ -240,6 +241,7 @@ func (e *Engine) Disconnect() { closeIdleConnections(dc.HTTPClient) } e.client = nopclient.NewNopClient() + e.apiClient = engineapinop.NewNopClient() e.state = stateDisconnected e.emitEvent("engine_disconnect") } @@ -255,7 +257,8 @@ func closeIdleConnections(client *http.Client) { // when it is first created but not yet 'Connect' to a remote docker API. func (e *Engine) isConnected() bool { _, ok := e.client.(*nopclient.NopClient) - return !ok + _, okAPIClient := e.apiClient.(*engineapinop.NopClient) + return (!ok && !okAPIClient) } // IsHealthy returns true if the engine is healthy @@ -384,8 +387,10 @@ func (e *Engine) CheckConnectionErr(err error) { // dockerclient defines ErrConnectionRefused error. but if http client is from swarm, it's not using // dockerclient. We need string matching for these cases. Remove the first character to deal with - // case sensitive issue + // case sensitive issue. + // engine-api returns ErrConnectionFailed error, so we check for that as long as dockerclient exists if err == dockerclient.ErrConnectionRefused || + err == engineapi.ErrConnectionFailed || strings.Contains(err.Error(), "onnection refused") || strings.Contains(err.Error(), "annot connect to the docker engine endpoint") { // each connection refused instance may increase failure count so @@ -413,7 +418,7 @@ func (e *Engine) updateSpecs() error { return fmt.Errorf("cannot get resources for this engine, make sure %s is a Docker Engine, not a Swarm manager", e.Addr) } - v, err := e.client.Version() + v, err := e.apiClient.ServerVersion() e.CheckConnectionErr(err) if err != nil { return err diff --git a/cluster/engine_test.go b/cluster/engine_test.go index 6bd9e07598..ab032566b2 100644 --- a/cluster/engine_test.go +++ b/cluster/engine_test.go @@ -7,6 +7,10 @@ import ( "testing" "time" + engineapi "github.com/docker/engine-api/client" + "github.com/docker/engine-api/types" + engineapimock "github.com/docker/swarm/api/mockclient" + engineapinop "github.com/docker/swarm/api/nopclient" "github.com/samalba/dockerclient" "github.com/samalba/dockerclient/mockclient" "github.com/samalba/dockerclient/nopclient" @@ -27,7 +31,7 @@ var ( Labels: []string{"foo=bar"}, } - mockVersion = &dockerclient.Version{ + mockVersion = types.Version{ Version: "1.6.2", } @@ -63,8 +67,11 @@ func TestCheckConnectionErr(t *testing.T) { engine.CheckConnectionErr(err) assert.True(t, len(engine.ErrMsg()) > 0) assert.True(t, engine.failureCount == 1) + + err = engineapi.ErrConnectionFailed engine.CheckConnectionErr(err) assert.True(t, engine.failureCount == 2) + err = nil engine.CheckConnectionErr(err) assert.True(t, engine.failureCount == 0) @@ -107,14 +114,16 @@ func TestEngineConnectionFailure(t *testing.T) { // Always fail. client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(&dockerclient.Info{}, errors.New("fail")) // Connect() should fail - assert.Error(t, engine.ConnectWithClient(client)) + assert.Error(t, engine.ConnectWithClient(client, apiClient)) // isConnected() should return false nop := nopclient.NewNopClient() - assert.Error(t, engine.ConnectWithClient(nop)) + nopAPIClient := engineapinop.NewNopClient() + assert.Error(t, engine.ConnectWithClient(nop, nopAPIClient)) assert.False(t, engine.isConnected()) client.Mock.AssertExpectations(t) @@ -123,12 +132,14 @@ func TestEngineConnectionFailure(t *testing.T) { func TestOutdatedEngine(t *testing.T) { engine := NewEngine("test", 0, engOpts) client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(&dockerclient.Info{}, nil) - assert.Error(t, engine.ConnectWithClient(client)) + assert.Error(t, engine.ConnectWithClient(client, apiClient)) nop := nopclient.NewNopClient() - assert.Error(t, engine.ConnectWithClient(nop)) + nopAPIClient := engineapinop.NewNopClient() + assert.Error(t, engine.ConnectWithClient(nop, nopAPIClient)) assert.False(t, engine.isConnected()) client.Mock.AssertExpectations(t) @@ -140,15 +151,17 @@ func TestEngineCpusMemory(t *testing.T) { assert.False(t, engine.isConnected()) client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil) client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() - assert.NoError(t, engine.ConnectWithClient(client)) + apiClient.On("ServerVersion").Return(mockVersion, nil) + + assert.NoError(t, engine.ConnectWithClient(client, apiClient)) assert.True(t, engine.isConnected()) assert.True(t, engine.IsHealthy()) @@ -164,15 +177,17 @@ func TestEngineSpecs(t *testing.T) { assert.False(t, engine.isConnected()) client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil) client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() - assert.NoError(t, engine.ConnectWithClient(client)) + apiClient.On("ServerVersion").Return(mockVersion, nil) + + assert.NoError(t, engine.ConnectWithClient(client, apiClient)) assert.True(t, engine.isConnected()) assert.True(t, engine.IsHealthy()) @@ -193,10 +208,12 @@ func TestEngineState(t *testing.T) { assert.False(t, engine.isConnected()) client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() + apiClient.On("ServerVersion").Return(mockVersion, nil) + // The client will return one container at first, then a second one will appear. client.On("ListContainers", true, false, "").Return([]dockerclient.Container{{Id: "one"}}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() @@ -206,7 +223,7 @@ func TestEngineState(t *testing.T) { client.On("ListContainers", true, false, fmt.Sprintf("{%q:[%q]}", "id", "two")).Return([]dockerclient.Container{{Id: "two"}}, nil).Once() client.On("InspectContainer", "two").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: 100}}, nil).Once() - assert.NoError(t, engine.ConnectWithClient(client)) + assert.NoError(t, engine.ConnectWithClient(client, apiClient)) assert.True(t, engine.isConnected()) // The engine should only have a single container at this point. @@ -238,19 +255,22 @@ func TestCreateContainer(t *testing.T) { Cmd: []string{"date"}, Tty: false, }} - engine = NewEngine("test", 0, engOpts) - client = mockclient.NewMockClient() + engine = NewEngine("test", 0, engOpts) + client = mockclient.NewMockClient() + apiClient = engineapimock.NewMockClient() ) engine.setState(stateUnhealthy) client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) - assert.NoError(t, engine.ConnectWithClient(client)) + + apiClient.On("ServerVersion").Return(mockVersion, nil) + + assert.NoError(t, engine.ConnectWithClient(client, apiClient)) assert.True(t, engine.isConnected()) mockConfig := config.ContainerConfig @@ -340,6 +360,7 @@ func TestUsedCpus(t *testing.T) { engine := NewEngine("test", 0, engOpts) engine.setState(stateHealthy) client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() for _, hn := range hostNcpu { for _, cn := range containerNcpu { @@ -348,14 +369,16 @@ func TestUsedCpus(t *testing.T) { cpuShares := int64(math.Ceil(float64(cn*1024) / float64(mockInfo.NCPU))) client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{{Id: "test"}}, nil).Once() client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("InspectContainer", "test").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: cpuShares}}, nil).Once() - engine.ConnectWithClient(client) + + apiClient.On("ServerVersion").Return(mockVersion, nil) + + engine.ConnectWithClient(client, apiClient) assert.Equal(t, engine.UsedCpus(), cn) } @@ -377,9 +400,9 @@ func TestContainerRemovedDuringRefresh(t *testing.T) { // A container is removed before it can be inspected. client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{container1, container2}, nil) @@ -388,7 +411,9 @@ func TestContainerRemovedDuringRefresh(t *testing.T) { client.On("InspectContainer", "c1").Return(info1, errors.New("Not found")) client.On("InspectContainer", "c2").Return(info2, nil) - assert.NoError(t, engine.ConnectWithClient(client)) + apiClient.On("ServerVersion").Return(mockVersion, nil) + + assert.NoError(t, engine.ConnectWithClient(client, apiClient)) assert.Nil(t, engine.RefreshContainers(true)) // List of containers is still valid @@ -403,11 +428,14 @@ func TestDisconnect(t *testing.T) { engine := NewEngine("test", 0, engOpts) client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() + client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() client.On("StopAllMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() + apiClient.On("ServerVersion").Return(mockVersion, nil) + // The client will return one container at first, then a second one will appear. client.On("ListContainers", true, false, "").Return([]dockerclient.Container{{Id: "one"}}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() @@ -417,7 +445,7 @@ func TestDisconnect(t *testing.T) { client.On("ListContainers", true, false, fmt.Sprintf("{%q:[%q]}", "id", "two")).Return([]dockerclient.Container{{Id: "two"}}, nil).Once() client.On("InspectContainer", "two").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: 100}}, nil).Once() - assert.NoError(t, engine.ConnectWithClient(client)) + assert.NoError(t, engine.ConnectWithClient(client, apiClient)) assert.True(t, engine.isConnected()) defer func() { diff --git a/cluster/swarm/cluster_test.go b/cluster/swarm/cluster_test.go index 862fc00afd..4494d23690 100644 --- a/cluster/swarm/cluster_test.go +++ b/cluster/swarm/cluster_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/docker/engine-api/types" + engineapimock "github.com/docker/swarm/api/mockclient" "github.com/docker/swarm/cluster" "github.com/samalba/dockerclient" "github.com/samalba/dockerclient/mockclient" @@ -36,7 +38,7 @@ var ( Labels: []string{"foo=bar"}, } - mockVersion = &dockerclient.Version{ + mockVersion = types.Version{ Version: "1.6.2", } @@ -132,16 +134,18 @@ func TestImportImage(t *testing.T) { // create mock client client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) + apiClient.On("ServerVersion").Return(mockVersion, nil) + // connect client - engine.ConnectWithClient(client) + engine.ConnectWithClient(client, apiClient) // add engine to cluster c.engines[engine.ID] = engine @@ -182,16 +186,18 @@ func TestLoadImage(t *testing.T) { // create mock client client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) + apiClient.On("ServerVersion").Return(mockVersion, nil) + // connect client - engine.ConnectWithClient(client) + engine.ConnectWithClient(client, apiClient) // add engine to cluster c.engines[engine.ID] = engine @@ -235,16 +241,18 @@ func TestTagImage(t *testing.T) { // create mock client client := mockclient.NewMockClient() + apiClient := engineapimock.NewMockClient() client.On("Info").Return(mockInfo, nil) - client.On("Version").Return(mockVersion, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() client.On("ListImages", mock.Anything).Return(images, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) + apiClient.On("ServerVersion").Return(mockVersion, nil) + // connect client - engine.ConnectWithClient(client) + engine.ConnectWithClient(client, apiClient) // add engine to cluster c.engines[engine.ID] = engine