From e29a2c17bc23592e2e20a0335975e7a8c494c685 Mon Sep 17 00:00:00 2001 From: fenxiong Date: Thu, 7 Mar 2019 10:19:28 -0800 Subject: [PATCH] Fix a bug where network stats is not presented in container stats. When switching to use Docker Go SDK, we used types.Stats to save the stats response from corresponding Docker Engine API. This struct doesn't contain all fields in the API response, notably the "networks" field is missing and therefore the field isn't presented in container stats response. This field was populated when we were using fsouza/go-dockerclient prior to v1.24.0. Fixing it by changing to use types.StatsJSON struct which contains the "networks" field along with fields in types.Stats. --- agent/dockerclient/dockerapi/docker_client.go | 16 +++++------ .../dockerapi/docker_client_test.go | 2 +- .../dockerapi/mocks/dockerapi_mocks.go | 4 +-- agent/handlers/task_server_setup_test.go | 20 ++++++++------ agent/handlers/v2/stats_response.go | 4 +-- agent/handlers/v2/stats_response_test.go | 3 ++- agent/stats/container_test.go | 12 ++++----- agent/stats/engine.go | 4 +-- agent/stats/engine_test.go | 15 ++++------- agent/stats/mock/engine.go | 4 +-- agent/stats/queue.go | 8 +++--- agent/stats/utils_test.go | 2 +- agent/stats/utils_unix.go | 2 +- agent/stats/utils_unix_test.go | 4 +-- agent/stats/utils_windows.go | 2 +- agent/stats/utils_windows_test.go | 4 +-- agent/tcs/client/client_test.go | 8 +++--- agent/tcs/handler/handler_test.go | 2 +- .../taskmetadata-validator.go | 8 +++--- .../setup-v3-task-endpoint-validator.ps1 | 1 + .../v3-task-endpoint-validator-windows.go | 27 +++++++++++++++++++ .../v3-task-endpoint-validator.go | 24 +++++++++++++++-- 22 files changed, 112 insertions(+), 64 deletions(-) diff --git a/agent/dockerclient/dockerapi/docker_client.go b/agent/dockerclient/dockerapi/docker_client.go index 7ee1eede261..bfa151c88e2 100644 --- a/agent/dockerclient/dockerapi/docker_client.go +++ b/agent/dockerclient/dockerapi/docker_client.go @@ -163,7 +163,7 @@ type DockerClient interface { // Stats returns a channel of stat data for the specified container. A context should be provided so the request can // be canceled. - Stats(context.Context, string, time.Duration) (<-chan *types.Stats, error) + Stats(context.Context, string, time.Duration) (<-chan *types.StatsJSON, error) // Version returns the version of the Docker daemon. Version(context.Context, time.Duration) (string, error) @@ -1287,8 +1287,8 @@ func (dg *dockerGoClient) APIVersion() (dockerclient.DockerVersion, error) { return dg.sdkClientFactory.FindClientAPIVersion(client), nil } -// Stats returns a channel of *types.Stats entries for the container. -func (dg *dockerGoClient) Stats(ctx context.Context, id string, inactivityTimeout time.Duration) (<-chan *types.Stats, error) { +// Stats returns a channel of *types.StatsJSON entries for the container. +func (dg *dockerGoClient) Stats(ctx context.Context, id string, inactivityTimeout time.Duration) (<-chan *types.StatsJSON, error) { subCtx, cancelRequest := context.WithCancel(ctx) client, err := dg.sdkDockerClient() @@ -1298,7 +1298,7 @@ func (dg *dockerGoClient) Stats(ctx context.Context, id string, inactivityTimeou } // Create channel to hold the stats - statsChnl := make(chan *types.Stats) + statsChnl := make(chan *types.StatsJSON) var resp types.ContainerStats if !dg.config.PollMetrics { @@ -1322,7 +1322,7 @@ func (dg *dockerGoClient) Stats(ctx context.Context, id string, inactivityTimeou // Returns a *Decoder and takes in a readCloser decoder := json.NewDecoder(resp.Body) - data := new(types.Stats) + data := new(types.StatsJSON) for err := decoder.Decode(data); err != io.EOF; err = decoder.Decode(data) { if err != nil { seelog.Warnf("DockerGoClient: Unable to decode stats for container %s: %v", id, err) @@ -1334,7 +1334,7 @@ func (dg *dockerGoClient) Stats(ctx context.Context, id string, inactivityTimeou } statsChnl <- data - data = new(types.Stats) + data = new(types.StatsJSON) } }() } else { @@ -1359,7 +1359,7 @@ func (dg *dockerGoClient) Stats(ctx context.Context, id string, inactivityTimeou // Returns a *Decoder and takes in a readCloser decoder := json.NewDecoder(resp.Body) - data := new(types.Stats) + data := new(types.StatsJSON) err := decoder.Decode(data) if err != nil { seelog.Warnf("DockerGoClient: Unable to decode stats for container %s: %v", id, err) @@ -1367,7 +1367,7 @@ func (dg *dockerGoClient) Stats(ctx context.Context, id string, inactivityTimeou } statsChnl <- data - data = new(types.Stats) + data = new(types.StatsJSON) } }() } diff --git a/agent/dockerclient/dockerapi/docker_client_test.go b/agent/dockerclient/dockerapi/docker_client_test.go index 70db0d83bf7..5bf13f18f05 100644 --- a/agent/dockerclient/dockerapi/docker_client_test.go +++ b/agent/dockerclient/dockerapi/docker_client_test.go @@ -1049,7 +1049,7 @@ func (ms mockStream) Read(data []byte) (n int, err error) { func (ms mockStream) Close() error { return nil } -func waitForStats(t *testing.T, stat *types.Stats) { +func waitForStats(t *testing.T, stat *types.StatsJSON) { ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) defer cancel() for { diff --git a/agent/dockerclient/dockerapi/mocks/dockerapi_mocks.go b/agent/dockerclient/dockerapi/mocks/dockerapi_mocks.go index 2981ee99a5a..712e0355879 100644 --- a/agent/dockerclient/dockerapi/mocks/dockerapi_mocks.go +++ b/agent/dockerclient/dockerapi/mocks/dockerapi_mocks.go @@ -291,9 +291,9 @@ func (mr *MockDockerClientMockRecorder) StartContainer(arg0, arg1, arg2 interfac } // Stats mocks base method -func (m *MockDockerClient) Stats(arg0 context.Context, arg1 string, arg2 time.Duration) (<-chan *types.Stats, error) { +func (m *MockDockerClient) Stats(arg0 context.Context, arg1 string, arg2 time.Duration) (<-chan *types.StatsJSON, error) { ret := m.ctrl.Call(m, "Stats", arg0, arg1, arg2) - ret0, _ := ret[0].(<-chan *types.Stats) + ret0, _ := ret[0].(<-chan *types.StatsJSON) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/agent/handlers/task_server_setup_test.go b/agent/handlers/task_server_setup_test.go index c2b0a656ffc..93bf9db5d99 100644 --- a/agent/handlers/task_server_setup_test.go +++ b/agent/handlers/task_server_setup_test.go @@ -670,7 +670,8 @@ func TestV2ContainerStats(t *testing.T) { statsEngine := mock_stats.NewMockEngine(ctrl) ecsClient := mock_api.NewMockECSClient(ctrl) - dockerStats := &types.Stats{NumProcs: 2} + dockerStats := &types.StatsJSON{} + dockerStats.NumProcs = 2 gomock.InOrder( state.EXPECT().GetTaskByIPAddress(remoteIP).Return(taskARN, true), statsEngine.EXPECT().ContainerDockerStats(taskARN, containerID).Return(dockerStats, nil), @@ -684,7 +685,7 @@ func TestV2ContainerStats(t *testing.T) { res, err := ioutil.ReadAll(recorder.Body) assert.NoError(t, err) assert.Equal(t, http.StatusOK, recorder.Code) - var statsFromResult *types.Stats + var statsFromResult *types.StatsJSON err = json.Unmarshal(res, &statsFromResult) assert.NoError(t, err) assert.Equal(t, dockerStats.NumProcs, statsFromResult.NumProcs) @@ -712,7 +713,8 @@ func TestV2TaskStats(t *testing.T) { statsEngine := mock_stats.NewMockEngine(ctrl) ecsClient := mock_api.NewMockECSClient(ctrl) - dockerStats := &types.Stats{NumProcs: 2} + dockerStats := &types.StatsJSON{} + dockerStats.NumProcs = 2 containerMap := map[string]*apicontainer.DockerContainer{ containerName: { DockerID: containerID, @@ -732,7 +734,7 @@ func TestV2TaskStats(t *testing.T) { res, err := ioutil.ReadAll(recorder.Body) assert.NoError(t, err) assert.Equal(t, http.StatusOK, recorder.Code) - var statsFromResult map[string]*types.Stats + var statsFromResult map[string]*types.StatsJSON err = json.Unmarshal(res, &statsFromResult) assert.NoError(t, err) containerStats, ok := statsFromResult[containerID] @@ -938,7 +940,8 @@ func TestV3TaskStats(t *testing.T) { statsEngine := mock_stats.NewMockEngine(ctrl) ecsClient := mock_api.NewMockECSClient(ctrl) - dockerStats := &types.Stats{NumProcs: 2} + dockerStats := &types.StatsJSON{} + dockerStats.NumProcs = 2 containerMap := map[string]*apicontainer.DockerContainer{ containerName: { @@ -959,7 +962,7 @@ func TestV3TaskStats(t *testing.T) { res, err := ioutil.ReadAll(recorder.Body) assert.NoError(t, err) assert.Equal(t, http.StatusOK, recorder.Code) - var statsFromResult map[string]*types.Stats + var statsFromResult map[string]*types.StatsJSON err = json.Unmarshal(res, &statsFromResult) assert.NoError(t, err) containerStats, ok := statsFromResult[containerID] @@ -976,7 +979,8 @@ func TestV3ContainerStats(t *testing.T) { statsEngine := mock_stats.NewMockEngine(ctrl) ecsClient := mock_api.NewMockECSClient(ctrl) - dockerStats := &types.Stats{NumProcs: 2} + dockerStats := &types.StatsJSON{} + dockerStats.NumProcs = 2 gomock.InOrder( state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true), @@ -991,7 +995,7 @@ func TestV3ContainerStats(t *testing.T) { res, err := ioutil.ReadAll(recorder.Body) assert.NoError(t, err) assert.Equal(t, http.StatusOK, recorder.Code) - var statsFromResult *types.Stats + var statsFromResult *types.StatsJSON err = json.Unmarshal(res, &statsFromResult) assert.NoError(t, err) assert.Equal(t, dockerStats.NumProcs, statsFromResult.NumProcs) diff --git a/agent/handlers/v2/stats_response.go b/agent/handlers/v2/stats_response.go index 938be1ca2e4..9f4df2fbe42 100644 --- a/agent/handlers/v2/stats_response.go +++ b/agent/handlers/v2/stats_response.go @@ -24,7 +24,7 @@ import ( // NewTaskStatsResponse returns a new task stats response object func NewTaskStatsResponse(taskARN string, state dockerstate.TaskEngineState, - statsEngine stats.Engine) (map[string]*types.Stats, error) { + statsEngine stats.Engine) (map[string]*types.StatsJSON, error) { containerMap, ok := state.ContainerMapByArn(taskARN) if !ok { @@ -33,7 +33,7 @@ func NewTaskStatsResponse(taskARN string, taskARN) } - resp := make(map[string]*types.Stats) + resp := make(map[string]*types.StatsJSON) for _, dockerContainer := range containerMap { containerID := dockerContainer.DockerID dockerStats, err := statsEngine.ContainerDockerStats(taskARN, containerID) diff --git a/agent/handlers/v2/stats_response_test.go b/agent/handlers/v2/stats_response_test.go index 60e2c7e9f73..f4220d8414e 100644 --- a/agent/handlers/v2/stats_response_test.go +++ b/agent/handlers/v2/stats_response_test.go @@ -33,7 +33,8 @@ func TestTaskStatsResponseSuccess(t *testing.T) { state := mock_dockerstate.NewMockTaskEngineState(ctrl) statsEngine := mock_stats.NewMockEngine(ctrl) - dockerStats := &types.Stats{NumProcs: 2} + dockerStats := &types.StatsJSON{} + dockerStats.NumProcs = 2 containerMap := map[string]*apicontainer.DockerContainer{ containerName: { DockerID: containerID, diff --git a/agent/stats/container_test.go b/agent/stats/container_test.go index b122edc288c..904ac3cd659 100644 --- a/agent/stats/container_test.go +++ b/agent/stats/container_test.go @@ -56,12 +56,12 @@ func TestContainerStatsCollection(t *testing.T) { dockerID := "container1" ctx, cancel := context.WithCancel(context.TODO()) - statChan := make(chan *types.Stats) + statChan := make(chan *types.StatsJSON) mockDockerClient.EXPECT().Stats(ctx, dockerID, dockerclient.StatsInactivityTimeout).Return(statChan, nil) go func() { for _, stat := range statsData { // doing this with json makes me sad, but is the easiest way to - // deal with the types.Stats.MemoryStats inner struct + // deal with the types.StatsJSON.MemoryStats inner struct jsonStat := fmt.Sprintf(` { "memory_stats": {"usage":%d, "privateworkingset":%d}, @@ -72,7 +72,7 @@ func TestContainerStatsCollection(t *testing.T) { } } }`, stat.memBytes, stat.memBytes, stat.cpuTime, stat.cpuTime) - dockerStat := &types.Stats{} + dockerStat := &types.StatsJSON{} json.Unmarshal([]byte(jsonStat), dockerStat) dockerStat.Read = stat.timestamp statChan <- dockerStat @@ -134,9 +134,9 @@ func TestContainerStatsCollectionReconnection(t *testing.T) { dockerID := "container1" ctx, cancel := context.WithCancel(context.TODO()) - statChan := make(chan *types.Stats) + statChan := make(chan *types.StatsJSON) statErr := fmt.Errorf("test error") - closedChan := make(chan *types.Stats) + closedChan := make(chan *types.StatsJSON) close(closedChan) mockContainer := &apicontainer.DockerContainer{ @@ -176,7 +176,7 @@ func TestContainerStatsCollectionStopsIfContainerIsTerminal(t *testing.T) { dockerID := "container1" ctx, cancel := context.WithCancel(context.TODO()) - closedChan := make(chan *types.Stats) + closedChan := make(chan *types.StatsJSON) close(closedChan) statsErr := fmt.Errorf("test error") diff --git a/agent/stats/engine.go b/agent/stats/engine.go index 7846201a371..540661f5fca 100644 --- a/agent/stats/engine.go +++ b/agent/stats/engine.go @@ -63,7 +63,7 @@ type DockerContainerMetadataResolver struct { // defined to make testing easier. type Engine interface { GetInstanceMetrics() (*ecstcs.MetricsMetadata, []*ecstcs.TaskMetric, error) - ContainerDockerStats(taskARN string, containerID string) (*types.Stats, error) + ContainerDockerStats(taskARN string, containerID string) (*types.StatsJSON, error) GetTaskHealthMetrics() (*ecstcs.HealthMetadata, []*ecstcs.TaskHealth, error) } @@ -648,7 +648,7 @@ func (engine *DockerStatsEngine) resetStatsUnsafe() { } // ContainerDockerStats returns the last stored raw docker stats object for a container -func (engine *DockerStatsEngine) ContainerDockerStats(taskARN string, containerID string) (*types.Stats, error) { +func (engine *DockerStatsEngine) ContainerDockerStats(taskARN string, containerID string) (*types.StatsJSON, error) { engine.lock.RLock() defer engine.lock.RUnlock() diff --git a/agent/stats/engine_test.go b/agent/stats/engine_test.go index 897e48df1ed..7c77628e1c3 100644 --- a/agent/stats/engine_test.go +++ b/agent/stats/engine_test.go @@ -52,7 +52,7 @@ func TestStatsEngineAddRemoveContainers(t *testing.T) { resolver.EXPECT().ResolveContainer(gomock.Any()).AnyTimes().Return(&apicontainer.DockerContainer{ Container: &apicontainer.Container{}, }, nil) - mockStatsChannel := make(chan *types.Stats) + mockStatsChannel := make(chan *types.StatsJSON) defer close(mockStatsChannel) mockDockerClient.EXPECT().Stats(gomock.Any(), gomock.Any(), gomock.Any()).Return(mockStatsChannel, nil).AnyTimes() @@ -194,14 +194,9 @@ func TestStatsEngineMetadataInStatsSets(t *testing.T) { {22400432, 1839104, ts1}, {116499979, 3649536, ts2}, } - dockerStats := []*types.Stats{ - { - Read: ts1, - }, - { - Read: ts2, - }, - } + dockerStats := []*types.StatsJSON{{}, {},} + dockerStats[0].Read = ts1 + dockerStats[1].Read = ts2 containers, _ := engine.tasksToContainers["t1"] for _, statsContainer := range containers { for i := 0; i < 2; i++ { @@ -398,7 +393,7 @@ func TestSynchronizeOnRestart(t *testing.T) { defer ctrl.Finish() containerID := "containerID" - statsChan := make(chan *types.Stats) + statsChan := make(chan *types.StatsJSON) statsStarted := make(chan struct{}) client := mock_dockerapi.NewMockDockerClient(ctrl) resolver := mock_resolver.NewMockContainerMetadataResolver(ctrl) diff --git a/agent/stats/mock/engine.go b/agent/stats/mock/engine.go index f133e4f2822..9b7407b3bf6 100644 --- a/agent/stats/mock/engine.go +++ b/agent/stats/mock/engine.go @@ -49,9 +49,9 @@ func (m *MockEngine) EXPECT() *MockEngineMockRecorder { } // ContainerDockerStats mocks base method -func (m *MockEngine) ContainerDockerStats(arg0, arg1 string) (*types.Stats, error) { +func (m *MockEngine) ContainerDockerStats(arg0, arg1 string) (*types.StatsJSON, error) { ret := m.ctrl.Call(m, "ContainerDockerStats", arg0, arg1) - ret0, _ := ret[0].(*types.Stats) + ret0, _ := ret[0].(*types.StatsJSON) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/agent/stats/queue.go b/agent/stats/queue.go index 1210f2ba14c..d0ac5416082 100644 --- a/agent/stats/queue.go +++ b/agent/stats/queue.go @@ -36,7 +36,7 @@ type Queue struct { buffer []UsageStats maxSize int lastResetTime time.Time - lastStat *types.Stats + lastStat *types.StatsJSON lock sync.RWMutex } @@ -57,7 +57,7 @@ func (queue *Queue) Reset() { } // Add adds a new set of container stats to the queue. -func (queue *Queue) Add(dockerStat *types.Stats) error { +func (queue *Queue) Add(dockerStat *types.StatsJSON) error { queue.setLastStat(dockerStat) stat, err := dockerStatsToContainerStats(dockerStat) if err != nil { @@ -67,7 +67,7 @@ func (queue *Queue) Add(dockerStat *types.Stats) error { return nil } -func (queue *Queue) setLastStat(stat *types.Stats) { +func (queue *Queue) setLastStat(stat *types.StatsJSON) { queue.lock.Lock() defer queue.lock.Unlock() @@ -108,7 +108,7 @@ func (queue *Queue) add(rawStat *ContainerStats) { } // GetLastStat returns the last recorded raw statistics object from docker -func (queue *Queue) GetLastStat() *types.Stats { +func (queue *Queue) GetLastStat() *types.StatsJSON { queue.lock.RLock() defer queue.lock.RUnlock() diff --git a/agent/stats/utils_test.go b/agent/stats/utils_test.go index f32b6e29814..6adbb6a40a4 100644 --- a/agent/stats/utils_test.go +++ b/agent/stats/utils_test.go @@ -56,7 +56,7 @@ func TestDockerStatsToContainerStatsMemUsage(t *testing.T) { "privateworkingset": %d } }`, 1, 2, 3, 4, 100, 30, 100, 20, 10, 10) - dockerStat := &types.Stats{} + dockerStat := &types.StatsJSON{} json.Unmarshal([]byte(jsonStat), dockerStat) containerStats, err := dockerStatsToContainerStats(dockerStat) if err != nil { diff --git a/agent/stats/utils_unix.go b/agent/stats/utils_unix.go index 45d00d8a1a0..562ee8e8d58 100644 --- a/agent/stats/utils_unix.go +++ b/agent/stats/utils_unix.go @@ -22,7 +22,7 @@ import ( ) // dockerStatsToContainerStats returns a new object of the ContainerStats object from docker stats. -func dockerStatsToContainerStats(dockerStats *types.Stats) (*ContainerStats, error) { +func dockerStatsToContainerStats(dockerStats *types.StatsJSON) (*ContainerStats, error) { // The length of PercpuUsage represents the number of cores in an instance. if len(dockerStats.CPUStats.CPUUsage.PercpuUsage) == 0 || numCores == uint64(0) { seelog.Debug("Invalid container statistics reported, no cpu core usage reported") diff --git a/agent/stats/utils_unix_test.go b/agent/stats/utils_unix_test.go index f2c5b6fb4a6..f3d3c07f081 100644 --- a/agent/stats/utils_unix_test.go +++ b/agent/stats/utils_unix_test.go @@ -35,7 +35,7 @@ func TestDockerStatsToContainerStatsZeroCoresGeneratesError(t *testing.T) { } } }`, 100) - dockerStat := &types.Stats{} + dockerStat := &types.StatsJSON{} json.Unmarshal([]byte(jsonStat), dockerStat) _, err := dockerStatsToContainerStats(dockerStat) assert.Error(t, err, "expected error converting container stats with empty PercpuUsage") @@ -57,7 +57,7 @@ func TestDockerStatsToContainerStatsCpuUsage(t *testing.T) { } } }`, 1, 2, 3, 4, 100) - dockerStat := &types.Stats{} + dockerStat := &types.StatsJSON{} json.Unmarshal([]byte(jsonStat), dockerStat) containerStats, err := dockerStatsToContainerStats(dockerStat) assert.NoError(t, err, "converting container stats failed") diff --git a/agent/stats/utils_windows.go b/agent/stats/utils_windows.go index abe6f9f4a39..a38dde8b320 100644 --- a/agent/stats/utils_windows.go +++ b/agent/stats/utils_windows.go @@ -22,7 +22,7 @@ import ( ) // dockerStatsToContainerStats returns a new object of the ContainerStats object from docker stats. -func dockerStatsToContainerStats(dockerStats *types.Stats) (*ContainerStats, error) { +func dockerStatsToContainerStats(dockerStats *types.StatsJSON) (*ContainerStats, error) { if numCores == uint64(0) { seelog.Error("Invalid number of cpu cores acquired from the system") return nil, fmt.Errorf("invalid number of cpu cores acquired from the system") diff --git a/agent/stats/utils_windows_test.go b/agent/stats/utils_windows_test.go index fc1dbaf46aa..5d4159325a2 100644 --- a/agent/stats/utils_windows_test.go +++ b/agent/stats/utils_windows_test.go @@ -34,7 +34,7 @@ func TestDockerStatsToContainerStatsZeroCoresGeneratesError(t *testing.T) { } } }`, 100) - dockerStat := &types.Stats{} + dockerStat := &types.StatsJSON{} json.Unmarshal([]byte(jsonStat), dockerStat) _, err := dockerStatsToContainerStats(dockerStat) assert.Error(t, err, "expected error converting container stats with zero cpu cores") @@ -55,7 +55,7 @@ func TestDockerStatsToContainerStatsCpuUsage(t *testing.T) { } } }`, 100) - dockerStat := &types.Stats{} + dockerStat := &types.StatsJSON{} json.Unmarshal([]byte(jsonStat), dockerStat) containerStats, err := dockerStatsToContainerStats(dockerStat) assert.NoError(t, err, "converting container stats failed") diff --git a/agent/tcs/client/client_test.go b/agent/tcs/client/client_test.go index 73e88d24141..853182655f1 100644 --- a/agent/tcs/client/client_test.go +++ b/agent/tcs/client/client_test.go @@ -56,7 +56,7 @@ func (*mockStatsEngine) GetInstanceMetrics() (*ecstcs.MetricsMetadata, []*ecstcs return nil, nil, fmt.Errorf("uninitialized") } -func (*mockStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.Stats, error) { +func (*mockStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.StatsJSON, error) { return nil, fmt.Errorf("not implemented") } @@ -70,7 +70,7 @@ func (*emptyStatsEngine) GetInstanceMetrics() (*ecstcs.MetricsMetadata, []*ecstc return nil, nil, fmt.Errorf("empty stats") } -func (*emptyStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.Stats, error) { +func (*emptyStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.StatsJSON, error) { return nil, fmt.Errorf("not implemented") } @@ -90,7 +90,7 @@ func (*idleStatsEngine) GetInstanceMetrics() (*ecstcs.MetricsMetadata, []*ecstcs return metadata, []*ecstcs.TaskMetric{}, nil } -func (*idleStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.Stats, error) { +func (*idleStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.StatsJSON, error) { return nil, fmt.Errorf("not implemented") } @@ -118,7 +118,7 @@ func (engine *nonIdleStatsEngine) GetInstanceMetrics() (*ecstcs.MetricsMetadata, return metadata, taskMetrics, nil } -func (*nonIdleStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.Stats, error) { +func (*nonIdleStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.StatsJSON, error) { return nil, fmt.Errorf("not implemented") } diff --git a/agent/tcs/handler/handler_test.go b/agent/tcs/handler/handler_test.go index 8515f71ec1c..bd9b3f45b43 100644 --- a/agent/tcs/handler/handler_test.go +++ b/agent/tcs/handler/handler_test.go @@ -65,7 +65,7 @@ func (*mockStatsEngine) GetInstanceMetrics() (*ecstcs.MetricsMetadata, []*ecstcs return req.Metadata, req.TaskMetrics, nil } -func (*mockStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.Stats, error) { +func (*mockStatsEngine) ContainerDockerStats(taskARN string, id string) (*types.StatsJSON, error) { return nil, fmt.Errorf("not implemented") } diff --git a/misc/taskmetadata-validator/taskmetadata-validator.go b/misc/taskmetadata-validator/taskmetadata-validator.go index ff00e4462e3..c42a7fce6af 100644 --- a/misc/taskmetadata-validator/taskmetadata-validator.go +++ b/misc/taskmetadata-validator/taskmetadata-validator.go @@ -157,7 +157,7 @@ func containerMetadata(client *http.Client, id string) (*ContainerResponse, erro return &containerMetadata, nil } -func taskStats(client *http.Client) (map[string]*types.Stats, error) { +func taskStats(client *http.Client) (map[string]*types.StatsJSON, error) { body, err := metadataResponse(client, v2StatsEndpoint, "task stats") if err != nil { return nil, err @@ -165,7 +165,7 @@ func taskStats(client *http.Client) (map[string]*types.Stats, error) { fmt.Printf("Received task stats: %s \n", string(body)) - var taskStats map[string]*types.Stats + var taskStats map[string]*types.StatsJSON err = json.Unmarshal(body, &taskStats) if err != nil { return nil, fmt.Errorf("task stats: unable to parse response body: %v", err) @@ -174,7 +174,7 @@ func taskStats(client *http.Client) (map[string]*types.Stats, error) { return taskStats, nil } -func containerStats(client *http.Client, id string) (*types.Stats, error) { +func containerStats(client *http.Client, id string) (*types.StatsJSON, error) { body, err := metadataResponse(client, v2StatsEndpoint+"/"+id, "container stats") if err != nil { return nil, err @@ -182,7 +182,7 @@ func containerStats(client *http.Client, id string) (*types.Stats, error) { fmt.Printf("Received container stats: %s \n", string(body)) - var containerStats types.Stats + var containerStats types.StatsJSON err = json.Unmarshal(body, &containerStats) if err != nil { return nil, fmt.Errorf("container stats: unable to parse response body: %v", err) diff --git a/misc/v3-task-endpoint-validator-windows/setup-v3-task-endpoint-validator.ps1 b/misc/v3-task-endpoint-validator-windows/setup-v3-task-endpoint-validator.ps1 index 6ec6c0027da..954459613cd 100644 --- a/misc/v3-task-endpoint-validator-windows/setup-v3-task-endpoint-validator.ps1 +++ b/misc/v3-task-endpoint-validator-windows/setup-v3-task-endpoint-validator.ps1 @@ -17,6 +17,7 @@ $ErrorActionPreference = 'Stop' Invoke-Expression ${PSScriptRoot}\..\windows-deploy\hostsetup.ps1 # Create amazon/amazon-ecs-v3-task-endpoint-validator-windows for tests +Invoke-Expression "go get github.com/docker/docker/api/types github.com/pkg/errors" Invoke-Expression "go build -o ${PSScriptRoot}\v3-task-endpoint-validator-windows.exe ${PSScriptRoot}\v3-task-endpoint-validator-windows.go" Invoke-Expression "docker build -t amazon/amazon-ecs-v3-task-endpoint-validator-windows --file ${PSScriptRoot}\v3-task-endpoint-validator-windows.dockerfile ${PSScriptRoot}" $ErrorActionPreference = $oldPref diff --git a/misc/v3-task-endpoint-validator-windows/v3-task-endpoint-validator-windows.go b/misc/v3-task-endpoint-validator-windows/v3-task-endpoint-validator-windows.go index 5cfcf7fd6d0..499900ef3a8 100644 --- a/misc/v3-task-endpoint-validator-windows/v3-task-endpoint-validator-windows.go +++ b/misc/v3-task-endpoint-validator-windows/v3-task-endpoint-validator-windows.go @@ -20,6 +20,9 @@ import ( "net/http" "os" "time" + + "github.com/docker/docker/api/types" + "github.com/pkg/errors" ) const ( @@ -146,6 +149,17 @@ func verifyContainerStats(client *http.Client, containerStatsEndpoint string) er fmt.Printf("Received container stats: %s \n", string(body)) + var containerStats types.StatsJSON + err = json.Unmarshal(body, &containerStats) + if err != nil { + return fmt.Errorf("container stats: unable to parse response body: %v", err) + } + + // networks field should be populated + if containerStats.Networks == nil { + return errors.New("container stats: field networks should not be empty") + } + return nil } @@ -157,6 +171,19 @@ func verifyTaskStats(client *http.Client, taskStatsEndpoint string) error { fmt.Printf("Received task stats: %s \n", string(body)) + var taskStats map[string]*types.StatsJSON + err = json.Unmarshal(body, &taskStats) + if err != nil { + return fmt.Errorf("task stats: unable to parse response body: %v", err) + } + + for container, containerStats := range taskStats { + // networks field should be populated + if containerStats.Networks == nil { + return fmt.Errorf("task stats: field networks for container %s should not be empty", container) + } + } + return nil } diff --git a/misc/v3-task-endpoint-validator/v3-task-endpoint-validator.go b/misc/v3-task-endpoint-validator/v3-task-endpoint-validator.go index aed84f50c29..c766cfe215f 100644 --- a/misc/v3-task-endpoint-validator/v3-task-endpoint-validator.go +++ b/misc/v3-task-endpoint-validator/v3-task-endpoint-validator.go @@ -32,6 +32,7 @@ const ( ) var isAWSVPCNetworkMode bool +var isBridgeNetworkMode bool var checkContainerInstanceTags bool var networkModes map[string]bool @@ -151,12 +152,19 @@ func verifyContainerStats(client *http.Client, containerStatsEndpoint string) er fmt.Printf("Received container stats: %s \n", string(body)) - var containerStats types.Stats + var containerStats types.StatsJSON err = json.Unmarshal(body, &containerStats) if err != nil { return fmt.Errorf("container stats: unable to parse response body: %v", err) } + if isBridgeNetworkMode { + // networks field should be populated in bridge mode + if containerStats.Networks == nil { + return errors.New("container stats: field networks should not be empty") + } + } + return nil } @@ -168,12 +176,21 @@ func verifyTaskStats(client *http.Client, taskStatsEndpoint string) error { fmt.Printf("Received task stats: %s \n", string(body)) - var taskStats map[string]*types.Stats + var taskStats map[string]*types.StatsJSON err = json.Unmarshal(body, &taskStats) if err != nil { return fmt.Errorf("task stats: unable to parse response body: %v", err) } + if isBridgeNetworkMode { + for container, containerStats := range taskStats { + // networks field should be populated in bridge mode + if containerStats.Networks == nil { + return fmt.Errorf("task stats: field networks for container %s should not be empty", container) + } + } + } + return nil } @@ -341,6 +358,8 @@ func verifyNetworksResponse(networksRawMsg json.RawMessage) error { } if actualFieldVal == "awsvpc" { isAWSVPCNetworkMode = true + } else if actualFieldVal == "bridge" { + isBridgeNetworkMode = true } } else { return fmt.Errorf("incorrect number of networks, expected 1, received %d", @@ -432,6 +451,7 @@ func main() { time.Sleep(5 * time.Second) isAWSVPCNetworkMode = false + isBridgeNetworkMode = false v3BaseEndpoint := os.Getenv(containerMetadataEnvVar) containerMetadataPath := v3BaseEndpoint taskMetadataPath := v3BaseEndpoint