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

dockerclient: 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
utils: reuse the semantic version comparator to compare Docker API versions with only major and minor versions too
  • Loading branch information
sharanyad committed Oct 12, 2017
1 parent 1045da5 commit 2ec3d45
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 3 deletions.
60 changes: 60 additions & 0 deletions agent/engine/dockerclient/dockerclientfactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ 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"
)

const (
// minDockerAPILinux is the min Docker API version supported by agent
minDockerAPILinux = Version_1_17
// minAPIVersionKey is the docker.Env key for min API version
minAPIVersionKey = "MinAPIVersion"
// apiVersionKey is the docker.Env key for API version
apiVersionKey = "ApiVersion"
)
// 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,6 +119,11 @@ 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 min version and api version, then use it to return
// Docker versions
if clients, ok := findDockerVersionsfromMinMaxVersions(endpoint); ok {
return clients
}
clients := make(map[DockerVersion]dockeriface.Client)
for _, version := range getKnownAPIVersions() {
client, err := newVersionedClient(endpoint, string(version))
Expand All @@ -121,8 +135,54 @@ func findDockerVersions(endpoint string) map[DockerVersion]dockeriface.Client {
err = client.Ping()
if err != nil {
log.Infof("Failed to ping with Docker version %s: %v", version, err)
continue
}
clients[version] = client
}
return clients
}

func findDockerVersionsfromMinMaxVersions(endpoint string) (map[DockerVersion]dockeriface.Client, bool) {
clients := make(map[DockerVersion]dockeriface.Client)
// get a Docker client with the default supported version
client, err := newVersionedClient(endpoint, string(minDockerAPILinux))
if err != nil {
log.Infof("Error while creating client: %v", err)
return nil, false
}

clientVersion, err := client.Version()
if err != nil {
log.Infof("Error while getting client version: %v", err)
return nil, false
}

// check if the docker.Env obj has MinAPIVersion key
if clientVersion.Exists(minAPIVersionKey) {
minAPIVersion := clientVersion.Get(minAPIVersionKey)
apiVersion := clientVersion.Get(apiVersionKey)
for _, version := range getKnownAPIVersions() {
lessThanMinCheck := "<"+minAPIVersion
moreThanMaxCheck := ">"+apiVersion
minVersionCheck, minErr := utils.Version(version).Matches(lessThanMinCheck)
maxVersionCheck, maxErr := utils.Version(version).Matches(moreThanMaxCheck)
if minErr != nil || maxErr != nil {
log.Infof("Error while comparing Docker API versions: %v, %v", minErr, maxErr)
continue
}
// do not add the version when it is less than min api version or greater
// than api version
if minVersionCheck || maxVersionCheck {
continue
}
client, err := newVersionedClient(endpoint, string(version))
if err != nil {
log.Infof("Error while creating client: %v", err)
continue
}
clients[version] = client
}
return clients, true
}
return nil, false
}
37 changes: 36 additions & 1 deletion 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,6 +61,7 @@ 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().Version().Return(&docker.Env{}, nil).AnyTimes()
mockClients[string(allVersions[i])].EXPECT().Ping()
}

Expand Down Expand Up @@ -92,3 +95,35 @@ 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])
}
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func TestGetClientMinimumVersion(t *testing.T) {
if version == string(MinDockerAPIWindows) {
mockClient = expectedClient
}
mockClient.EXPECT().Ping()
mockClient.EXPECT().Version().Return(&docker.Env{}, nil).AnyTimes()
mockClient.EXPECT().Ping().AnyTimes()
return mockClient, nil
}

Expand Down
12 changes: 12 additions & 0 deletions agent/utils/compare_versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import (
"strings"
)

// zeroPatch is a string to append patch number zero if the major minor version lacks it
const zeroPatch = ".0"

type Version string

type semver struct {
Expand Down Expand Up @@ -93,6 +96,9 @@ func (lhs Version) Matches(selector string) (bool, error) {

func parseSemver(version string) (semver, error) {
var result semver
if ok := checkForNoPatch(version); ok {
version += zeroPatch
}
// 0.0.0-some-prealpha-stuff.1+12345
versionAndMetadata := strings.SplitN(version, "+", 2)
// [0.0.0-some-prealpha-stuff.1, 12345]
Expand Down Expand Up @@ -128,6 +134,12 @@ func parseSemver(version string) (semver, error) {
return result, nil
}

func checkForNoPatch(version string) bool {
versionParts := strings.Split(version, ".")
// Only major and minor versions are present
return len(versionParts) == 2
}

// compareSemver compares two semvers, 'lhs' and 'rhs', and returns -1 if lhs is less
// than rhs, 0 if they are equal, and 1 lhs is greater than rhs
func compareSemver(lhs, rhs semver) int {
Expand Down
5 changes: 5 additions & 0 deletions agent/utils/compare_versions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ func TestVersionMatches(t *testing.T) {
selector: ">=1.5.0",
expectedOutput: true,
},
{
version: "1.17",
selector: "<1.12",
expectedOutput: false,
},
}

for i, testCase := range testCases {
Expand Down

0 comments on commit 2ec3d45

Please sign in to comment.