-
Notifications
You must be signed in to change notification settings - Fork 617
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a container loader for AppNet Agent
- Loading branch information
1 parent
d19d0a4
commit ddabb46
Showing
9 changed files
with
676 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package serviceconnect | ||
|
||
// https://golang.org/src/syscall/zerrors_linux_386.go#L1382 | ||
const noSuchFile = "no such file or directory" | ||
|
||
// UnsupportedPlatformError indicates an error when loading appnet container | ||
// image on an unsupported OS platform | ||
type UnsupportedPlatformError struct { | ||
error | ||
} | ||
|
||
// IsUnsupportedPlatform returns true if the error is of UnsupportedPlatformError | ||
// type | ||
func IsUnsupportedPlatform(err error) bool { | ||
_, ok := err.(UnsupportedPlatformError) | ||
return ok | ||
} | ||
|
||
// NewUnsupportedPlatformError creates a new UnsupportedPlatformError object | ||
func NewUnsupportedPlatformError(err error) UnsupportedPlatformError { | ||
return UnsupportedPlatformError{err} | ||
} | ||
|
||
// NoSuchFileError wraps the error from the os package with the message | ||
// "no such file error" | ||
type NoSuchFileError struct { | ||
error | ||
} | ||
|
||
// NewNoSuchFileError creates a new NoSuchFileError object | ||
func NewNoSuchFileError(err error) NoSuchFileError { | ||
return NoSuchFileError{err} | ||
} | ||
|
||
// IsNoSuchFileError returns true if the error is of NoSuchFileError type | ||
func IsNoSuchFileError(err error) bool { | ||
_, ok := err.(NoSuchFileError) | ||
return ok | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
//go:build unit | ||
// +build unit | ||
|
||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package serviceconnect | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestUnsupportedPlatform(t *testing.T) { | ||
testCases := map[error]bool{ | ||
errors.New("error"): false, | ||
NewUnsupportedPlatformError(errors.New("error")): true, | ||
} | ||
|
||
for err, expected := range testCases { | ||
t.Run(fmt.Sprintf("returns %t for type %s", expected, reflect.TypeOf(err)), func(t *testing.T) { | ||
assert.Equal(t, expected, IsUnsupportedPlatform(err)) | ||
}) | ||
} | ||
} | ||
|
||
func TestIsNoSuchFileError(t *testing.T) { | ||
testCases := map[error]bool{ | ||
errors.New("error"): false, | ||
NewNoSuchFileError(errors.New("No such file")): true, | ||
} | ||
|
||
for err, expected := range testCases { | ||
t.Run(fmt.Sprintf("return %t for type %s", expected, reflect.TypeOf(err)), func(t *testing.T) { | ||
assert.Equal(t, expected, IsNoSuchFileError(err)) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package serviceconnect | ||
|
||
//go:generate mockgen -destination=mocks/load_mocks.go -copyright_file=../../scripts/copyright_file github.com/aws/amazon-ecs-agent/agent/serviceconnect Loader |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package serviceconnect | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" | ||
"github.com/aws/amazon-ecs-agent/agent/logger" | ||
"github.com/aws/amazon-ecs-agent/agent/logger/field" | ||
"github.com/docker/docker/api/types" | ||
) | ||
|
||
var ( | ||
defaultAgentContainerImageName = "appnet_agent" | ||
defaultAgentContainerTag = "service_connect.v1" | ||
) | ||
|
||
// Loader defines an interface for loading the appnetAgent container image. This is mostly | ||
// to facilitate mocking and testing of the LoadImage method | ||
type Loader interface { | ||
LoadImage(ctx context.Context, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) | ||
IsLoaded(dockerClient dockerapi.DockerClient) (bool, error) | ||
} | ||
|
||
type loader struct { | ||
AgentContainerImageName string | ||
AgentContainerTag string | ||
AgentContainerTarballPath string | ||
} | ||
|
||
// New creates a new AppNet Agent image loader | ||
func New() Loader { | ||
return &loader{ | ||
AgentContainerImageName: defaultAgentContainerImageName, | ||
AgentContainerTag: defaultAgentContainerTag, | ||
AgentContainerTarballPath: defaultAgentContainerTarballPath, | ||
} | ||
} | ||
|
||
// This function uses the DockerClient to inspect the image with the given name and tag. | ||
func getAgentContainerImage(name string, tag string, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) { | ||
imageName := fmt.Sprintf("%s:%s", name, tag) | ||
logger.Debug("Inspecting appnet agent container image:", logger.Fields{ | ||
field.Image: imageName, | ||
}) | ||
|
||
image, err := dockerClient.InspectImage(imageName) | ||
if err != nil { | ||
return nil, fmt.Errorf("appnet agent container load: failed to inspect image: %s; %w", imageName, err) | ||
} | ||
|
||
return image, nil | ||
} | ||
|
||
// Common function for linux and windows to check if the container appnet Agent image has been loaded | ||
func (agent *loader) isImageLoaded(dockerClient dockerapi.DockerClient) (bool, error) { | ||
image, err := getAgentContainerImage( | ||
agent.AgentContainerImageName, agent.AgentContainerTag, dockerClient) | ||
|
||
if err != nil { | ||
return false, err | ||
} | ||
|
||
if image == nil || image.ID == "" { | ||
return false, nil | ||
} | ||
|
||
return true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
//go:build linux | ||
// +build linux | ||
|
||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package serviceconnect | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/aws/amazon-ecs-agent/agent/dockerclient" | ||
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" | ||
"github.com/aws/amazon-ecs-agent/agent/logger" | ||
"github.com/aws/amazon-ecs-agent/agent/logger/field" | ||
|
||
"github.com/docker/docker/api/types" | ||
) | ||
|
||
var ( | ||
defaultAgentContainerTarballPath = "/managed-agents/serviceconnect/appnet_agent.interface-v1.tar" | ||
) | ||
|
||
// LoadImage helps load the AppNetAgent container image for the agent | ||
func (agent *loader) LoadImage(ctx context.Context, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) { | ||
logger.Debug("Loading appnet agent container tarball:", logger.Fields{ | ||
field.Image: agent.AgentContainerTarballPath, | ||
}) | ||
if err := loadFromFile(ctx, agent.AgentContainerTarballPath, dockerClient); err != nil { | ||
return nil, err | ||
} | ||
|
||
return getAgentContainerImage( | ||
agent.AgentContainerImageName, agent.AgentContainerTag, dockerClient) | ||
} | ||
|
||
func (agent *loader) IsLoaded(dockerClient dockerapi.DockerClient) (bool, error) { | ||
return agent.isImageLoaded(dockerClient) | ||
} | ||
|
||
var open = os.Open | ||
|
||
func loadFromFile(ctx context.Context, path string, dockerClient dockerapi.DockerClient) error { | ||
containerReader, err := open(path) | ||
if err != nil { | ||
if err.Error() == noSuchFile { | ||
return NewNoSuchFileError(fmt.Errorf( | ||
"appnet agent container load: failed to read container image: %s : %w", path, err)) | ||
} | ||
return fmt.Errorf("appnet agent container load: failed to read container image: %s : %w", path, err) | ||
} | ||
if err := dockerClient.LoadImage(ctx, containerReader, dockerclient.LoadImageTimeout); err != nil { | ||
return fmt.Errorf("appnet agent container load: failed to load container image: %s : %w", path, err) | ||
} | ||
|
||
return nil | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
//go:build linux && unit | ||
// +build linux,unit | ||
|
||
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package serviceconnect | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"os" | ||
"testing" | ||
|
||
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" | ||
mock_sdkclient "github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclient/mocks" | ||
mock_sdkclientfactory "github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclientfactory/mocks" | ||
|
||
"github.com/docker/docker/api/types" | ||
"github.com/golang/mock/gomock" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
const ( | ||
containerTarballPath = "/path/to/container.tar" | ||
) | ||
|
||
func mockOpen() func() { | ||
open = func(name string) (*os.File, error) { | ||
return nil, nil | ||
} | ||
return func() { | ||
open = os.Open | ||
} | ||
} | ||
|
||
// TestLoadFromFileWithReaderError tests loadFromFile with reader error | ||
func TestLoadFromFileWithReaderError(t *testing.T) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
// Docker SDK tests | ||
mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) | ||
mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) | ||
sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) | ||
sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) | ||
|
||
ctx, cancel := context.WithCancel(context.TODO()) | ||
defer cancel() | ||
|
||
client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) | ||
assert.NoError(t, err) | ||
|
||
open = func(name string) (*os.File, error) { | ||
return nil, errors.New("Dummy Reader Error") | ||
} | ||
defer func() { | ||
open = os.Open | ||
}() | ||
|
||
err = loadFromFile(ctx, containerTarballPath, client) | ||
assert.Error(t, err) | ||
} | ||
|
||
// TestLoadFromFileHappyPath tests loadFromFile against happy path | ||
func TestLoadFromFileHappyPath(t *testing.T) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
// Docker SDK tests | ||
mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) | ||
mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) | ||
sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) | ||
sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) | ||
|
||
ctx, cancel := context.WithCancel(context.TODO()) | ||
defer cancel() | ||
|
||
client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) | ||
assert.NoError(t, err) | ||
mockDockerSDK.EXPECT().ImageLoad(gomock.Any(), gomock.Any(), false).Return(types.ImageLoadResponse{}, nil) | ||
defer mockOpen()() | ||
|
||
err = loadFromFile(ctx, containerTarballPath, client) | ||
assert.NoError(t, err) | ||
} | ||
|
||
// TestLoadFromFileDockerLoadImageError tests loadFromFile against error | ||
// from Docker clients LoadImage | ||
func TestLoadFromFileDockerLoadImageError(t *testing.T) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
// Docker SDK tests | ||
mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) | ||
mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) | ||
sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) | ||
sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) | ||
|
||
ctx, cancel := context.WithCancel(context.TODO()) | ||
defer cancel() | ||
|
||
client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) | ||
assert.NoError(t, err) | ||
mockDockerSDK.EXPECT().ImageLoad(gomock.Any(), gomock.Any(), false).Return(types.ImageLoadResponse{}, | ||
errors.New("Dummy Load Image Error")) | ||
|
||
defer mockOpen()() | ||
|
||
err = loadFromFile(ctx, containerTarballPath, client) | ||
assert.Error(t, err) | ||
} |
Oops, something went wrong.