Skip to content

Commit

Permalink
dockerclient: Agent supported Docker versions from client min,API Ver…
Browse files Browse the repository at this point in the history
…sions

Finding agent supported Docker versions by getting the default client version and if the min API version is present, add all the clients between min API version and API version supported, else follow current logic
  • Loading branch information
sharanyad committed Oct 17, 2017
1 parent 01d1bef commit 7aebdb5
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 18 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

## UNRELEASED
* Feature - Support for provisioning Tasks with ENIs
* Bug - Fixed a bug where unsupported Docker API client versions could be registered
[#1014](https://github.com/aws/amazon-ecs-agent/pull/1014)

## 1.14.5
* Enhancement - Retry failed container image pull operations [#975](https://github.com/aws/amazon-ecs-agent/pull/975)
* Enhancement - Set read and write timeouts for websocket connectons [#993](https://github.com/aws/amazon-ecs-agent/pull/993)
* Enhancement - Add support for the SumoLogic Docker log driver plugin
[#992](https://github.com/aws/amazon-ecs-agent/pull/992)
[#992](https://github.com/aws/amazon-ecs-agent/pull/992)
* Bug - Fixed a memory leak issue when submitting the task state change [#967](https://github.com/aws/amazon-ecs-agent/pull/967)
* Bug - Fixed a race condition where a container can be created twice when agent restarts. [#939](https://github.com/aws/amazon-ecs-agent/pull/939)
* Bug - Fixed an issue where `microsoft/windowsservercore:latest` was not
Expand Down
78 changes: 70 additions & 8 deletions agent/engine/dockerclient/dockerclientfactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@
package dockerclient

import (
"errors"

"github.com/aws/amazon-ecs-agent/agent/engine/dockeriface"
"github.com/aws/amazon-ecs-agent/agent/utils"
log "github.com/cihub/seelog"
docker "github.com/fsouza/go-dockerclient"
"github.com/pkg/errors"
)

const (
// minAPIVersionKey is the docker.Env key for min API version
// This is supported in Docker API versions >=1.25
// https://docs.docker.com/engine/api/version-history/#v125-api-changes
minAPIVersionKey = "MinAPIVersion"
// apiVersionKey is the docker.Env key for API version
apiVersionKey = "ApiVersion"
// zeroPatch is a string to append patch number zero if the major minor version lacks it
zeroPatch = ".0"
)
// Factory provides a collection of docker remote clients that include a
// recommended client version as well as a set of alternative supported
// docker clients.
Expand Down Expand Up @@ -110,19 +120,71 @@ func (f *factory) getClient(version DockerVersion) (dockeriface.Client, error) {
// findDockerVersions loops over all known API versions and finds which ones
// are supported by the docker daemon on the host
func findDockerVersions(endpoint string) map[DockerVersion]dockeriface.Client {
// if the client version returns a MinAPIVersion and APIVersion, then use it to return
// all the Docker clients between MinAPIVersion and APIVersion, else try pinging
// the clients in getKnownAPIVersions
var minAPIVersion, apiVersion string
// get a Docker client with the default supported version
client, err := newVersionedClient(endpoint, string(minDockerAPIVersion))
if err == nil {
clientVersion, err := client.Version()
if err == nil {
// check if the docker.Env obj has MinAPIVersion key
if clientVersion.Exists(minAPIVersionKey) {
minAPIVersion = clientVersion.Get(minAPIVersionKey)
}
// check if the docker.Env obj has APIVersion key
if clientVersion.Exists(apiVersionKey) {
apiVersion = clientVersion.Get(apiVersionKey)
}
}
}

clients := make(map[DockerVersion]dockeriface.Client)
for _, version := range getKnownAPIVersions() {
client, err := newVersionedClient(endpoint, string(version))
dockerClient, err := getDockerClientForVersion(endpoint, string(version), minAPIVersion, apiVersion)
if err != nil {
log.Infof("Error while creating client: %v", err)
log.Infof("Unable to get Docker client for version %s: %v", version, err)
continue
}
clients[version] = dockerClient
}
return clients
}

err = client.Ping()
func getDockerClientForVersion(
endpoint string,
version string,
minAPIVersion string,
apiVersion string) (dockeriface.Client, error) {
if (minAPIVersion != "" && apiVersion != "") {
// Adding patch number zero to Docker versions to reuse the existing semver
// comparator
// TODO: remove this logic later when non-semver comparator is implemented
versionWithPatch := version + zeroPatch
lessThanMinCheck := "<" + minAPIVersion + zeroPatch
moreThanMaxCheck := ">" + apiVersion + zeroPatch
minVersionCheck, err := utils.Version(versionWithPatch).Matches(lessThanMinCheck)
if err != nil {
return nil, errors.Wrapf(err, "version detection using MinAPIVersion: unable to get min version: %s", minAPIVersion)
}
maxVersionCheck, err := utils.Version(versionWithPatch).Matches(moreThanMaxCheck)
if err != nil {
log.Infof("Failed to ping with Docker version %s: %v", version, err)
return nil, errors.Wrapf(err, "version detection using MinAPIVersion: unable to get max version: %s", apiVersion)
}
// do not add the version when it is less than min api version or greater
// than api version
if minVersionCheck || maxVersionCheck {
return nil, errors.Errorf("version detection using MinAPIVersion: unsupported version: %s", version)
}
clients[version] = client
}
return clients
client, err := newVersionedClient(endpoint, string(version))
if err != nil {
return nil, errors.Wrapf(err, "version detection check: unable to create Docker client for version: %s", version)
}
err = client.Ping()
if err != nil {
return nil, errors.Wrapf(err, "version detection check: failed to ping with Docker version: %s", string(version))
}
return client, nil
}
62 changes: 60 additions & 2 deletions agent/engine/dockerclient/dockerclientfactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/aws/amazon-ecs-agent/agent/engine/dockeriface/mocks"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
docker "github.com/fsouza/go-dockerclient"
)

const expectedEndpoint = "expectedEndpoint"
Expand All @@ -35,7 +36,8 @@ func TestGetDefaultClientSuccess(t *testing.T) {
if version == string(getDefaultVersion()) {
mockClient = expectedClient
}
mockClient.EXPECT().Ping()
mockClient.EXPECT().Version().Return(&docker.Env{}, nil).AnyTimes()
mockClient.EXPECT().Ping().AnyTimes()

return mockClient, nil
}
Expand All @@ -59,7 +61,8 @@ func TestFindSupportedAPIVersions(t *testing.T) {
// Ensure that agent pings all known versions of Docker API
for i := 0; i < len(allVersions); i++ {
mockClients[string(allVersions[i])] = mock_dockeriface.NewMockClient(ctrl)
mockClients[string(allVersions[i])].EXPECT().Ping()
mockClients[string(allVersions[i])].EXPECT().Version().Return(&docker.Env{}, nil).AnyTimes()
mockClients[string(allVersions[i])].EXPECT().Ping().AnyTimes()
}

// Define the function for the mock client
Expand Down Expand Up @@ -92,3 +95,58 @@ func TestVerifyAgentVersions(t *testing.T) {
assert.True(t, isKnown(agentVersion))
}
}

func TestFindSupportedAPIVersionsFromMinAPIVersions(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

agentVersions := getAgentVersions()
allVersions := getKnownAPIVersions()

// Set up the mocks and expectations
mockClients := make(map[string]*mock_dockeriface.MockClient)

// Ensure that agent pings all known versions of Docker API
for i := 0; i < len(allVersions); i++ {
mockClients[string(allVersions[i])] = mock_dockeriface.NewMockClient(ctrl)
mockClients[string(allVersions[i])].EXPECT().Version().Return(&docker.Env{"MinAPIVersion=1.12","ApiVersion=1.27"}, nil).AnyTimes()
mockClients[string(allVersions[i])].EXPECT().Ping().AnyTimes()
}

// Define the function for the mock client
// For simplicity, we will pretend all versions of docker are available
newVersionedClient = func(endpoint, version string) (dockeriface.Client, error) {
return mockClients[version], nil
}

factory := NewFactory(expectedEndpoint)
actualVersions := factory.FindSupportedAPIVersions()

assert.Equal(t, len(agentVersions), len(actualVersions))
for i := 0; i < len(actualVersions); i++ {
assert.Equal(t, agentVersions[i], actualVersions[i])
}
}

func TestCompareDockerVersionsWithMinAPIVersion(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
minAPIVersion := "1.12"
apiVersion := "1.27"
versions := []string{"1.11", "1.29"}
rightVersion := "1.25"

for _, version := range versions {
_, err := getDockerClientForVersion("endpoint", version, minAPIVersion, apiVersion)
assert.EqualError(t, err, "version detection using MinAPIVersion: unsupported version: " + version)
}

mockClients := make(map[string]*mock_dockeriface.MockClient)
newVersionedClient = func(endpoint, version string) (dockeriface.Client, error) {
mockClients[version] = mock_dockeriface.NewMockClient(ctrl)
mockClients[version].EXPECT().Ping()
return mockClients[version], nil
}
client, _ := getDockerClientForVersion("endpoint", rightVersion, minAPIVersion, apiVersion)
assert.Equal(t, mockClients[rightVersion], client)
}
4 changes: 3 additions & 1 deletion agent/engine/dockerclient/dockerclientfactory_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/aws/amazon-ecs-agent/agent/engine/dockeriface/mocks"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
docker "github.com/fsouza/go-dockerclient"
)

func TestGetClientCached(t *testing.T) {
Expand All @@ -29,7 +30,8 @@ func TestGetClientCached(t *testing.T) {

newVersionedClient = func(endpoint, version string) (dockeriface.Client, error) {
mockClient := mock_dockeriface.NewMockClient(ctrl)
mockClient.EXPECT().Ping()
mockClient.EXPECT().Version().Return(&docker.Env{}, nil).AnyTimes()
mockClient.EXPECT().Ping().AnyTimes()
return mockClient, nil
}

Expand Down
6 changes: 4 additions & 2 deletions agent/engine/dockerclient/dockerclientfactory_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/aws/amazon-ecs-agent/agent/engine/dockeriface/mocks"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
docker "github.com/fsouza/go-dockerclient"
)

func TestGetClientMinimumVersion(t *testing.T) {
Expand All @@ -31,10 +32,11 @@ func TestGetClientMinimumVersion(t *testing.T) {

newVersionedClient = func(endpoint, version string) (dockeriface.Client, error) {
mockClient := mock_dockeriface.NewMockClient(ctrl)
if version == string(MinDockerAPIWindows) {
if version == string(minDockerAPIVersion) {
mockClient = expectedClient
}
mockClient.EXPECT().Ping()
mockClient.EXPECT().Version().Return(&docker.Env{}, nil).AnyTimes()
mockClient.EXPECT().Ping().AnyTimes()
return mockClient, nil
}

Expand Down
4 changes: 4 additions & 0 deletions agent/engine/dockerclient/versionsupport_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import (
"github.com/aws/amazon-ecs-agent/agent/engine/dockeriface"
)

const (
// minDockerAPIVersion is the min Docker API version supported by agent
minDockerAPIVersion = Version_1_17
)
// GetClient on linux will simply return the cached client from the map
func (f *factory) GetClient(version DockerVersion) (dockeriface.Client, error) {
return f.getClient(version)
Expand Down
8 changes: 4 additions & 4 deletions agent/engine/dockerclient/versionsupport_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import (
"github.com/aws/amazon-ecs-agent/agent/engine/dockeriface"
)

const MinDockerAPIWindows = Version_1_24
const minDockerAPIVersion = Version_1_24

// GetClient will replace some versions of Docker on Windows. We need this because
// agent assumes that it can always call older versions of the docker API.
func (f *factory) GetClient(version DockerVersion) (dockeriface.Client, error) {
for _, v := range getWindowsReplaceableVersions() {
if v == version {
version = MinDockerAPIWindows
version = minDockerAPIVersion
break
}
}
Expand All @@ -48,10 +48,10 @@ func getWindowsReplaceableVersions() []DockerVersion {

// getAgentVersions for Windows should return all of the replaceable versions plus additional versions
func getAgentVersions() []DockerVersion {
return append(getWindowsReplaceableVersions(), MinDockerAPIWindows)
return append(getWindowsReplaceableVersions(), minDockerAPIVersion)
}

// getDefaultVersion returns agent's default version of the Docker API
func getDefaultVersion() DockerVersion {
return MinDockerAPIWindows
return minDockerAPIVersion
}

0 comments on commit 7aebdb5

Please sign in to comment.