diff --git a/agent/handlers/task_server_setup_linux_test.go b/agent/handlers/task_server_setup_linux_test.go index ed06bf15357..421bd903276 100644 --- a/agent/handlers/task_server_setup_linux_test.go +++ b/agent/handlers/task_server_setup_linux_test.go @@ -17,6 +17,8 @@ package handlers import ( + "errors" + "fmt" "net" "testing" @@ -34,6 +36,11 @@ import ( "github.com/vishvananda/netlink" ) +const ( + internalError = "internal error" + defaultNetworkInterfaceNameErrorMessage = "failed to obtain default network interface name: unable to obtain default network interface name on host from v3 endpoint ID: %s" +) + func TestV4GetTaskMetadataWithTaskNetworkConfig(t *testing.T) { tcs := []struct { @@ -41,6 +48,8 @@ func TestV4GetTaskMetadataWithTaskNetworkConfig(t *testing.T) { setStateExpectations func(state *mock_dockerstate.MockTaskEngineState) setNetLinkExpectations func(netLink *mock_netlinkwrapper.MockNetLink) expectedTaskNetworkConfig *v4.TaskNetworkConfig + shouldError bool + errorMessage string }{ { name: "happy case with awsvpc mode", @@ -112,6 +121,38 @@ func TestV4GetTaskMetadataWithTaskNetworkConfig(t *testing.T) { }, expectedTaskNetworkConfig: expectedV4TaskNetworkConfig(true, bridgeMode, "", ""), }, + { + name: "unhappy case with host mode", + setStateExpectations: func(state *mock_dockerstate.MockTaskEngineState) { + hostTask := standardHostTask() + hostTask.EnableFaultInjection = true + hostTask.NetworkNamespace = networkNamespace + hostTask.DefaultIfname = defaultIfname + gomock.InOrder( + state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true), + state.EXPECT().TaskByArn(taskARN).Return(hostTask, true).Times(2), + state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true), + state.EXPECT().ContainerByID(containerID).Return(nil, false).AnyTimes(), + state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true), + state.EXPECT().ContainerByID(containerID).Return(nil, false).AnyTimes(), + ) + }, + setNetLinkExpectations: func(netLink *mock_netlinkwrapper.MockNetLink) { + routes := []netlink.Route{ + netlink.Route{ + Gw: net.ParseIP("10.194.20.1"), + Dst: nil, + LinkIndex: 0, + }, + } + gomock.InOrder( + netLink.EXPECT().RouteList(nil, netlink.FAMILY_ALL).Return(routes, errors.New(internalError)).Times(1), + ) + }, + expectedTaskNetworkConfig: expectedV4TaskNetworkConfig(true, apitask.HostNetworkMode, hostNetworkNamespace, ""), + shouldError: true, + errorMessage: fmt.Sprintf(defaultNetworkInterfaceNameErrorMessage, v3EndpointID), + }, } for _, tc := range tcs { @@ -138,7 +179,14 @@ func TestV4GetTaskMetadataWithTaskNetworkConfig(t *testing.T) { actualTaskResponse, err := tmdsAgentState.GetTaskMetadataWithTaskNetworkConfig(v3EndpointID, netConfigClient) - assert.NoError(t, err) + if tc.shouldError { + var errDefaultNetworkInterfaceName *v4.ErrorDefaultNetworkInterfaceName + assert.Error(t, err) + assert.ErrorAs(t, err, &errDefaultNetworkInterfaceName) + assert.Equal(t, tc.errorMessage, err.Error()) + } else { + assert.NoError(t, err) + } assert.Equal(t, tc.expectedTaskNetworkConfig, actualTaskResponse.TaskNetworkConfig) }) } diff --git a/agent/handlers/v4/tmdsstate_linux.go b/agent/handlers/v4/tmdsstate_linux.go index 0b223692f29..0265b7cf13b 100644 --- a/agent/handlers/v4/tmdsstate_linux.go +++ b/agent/handlers/v4/tmdsstate_linux.go @@ -17,21 +17,28 @@ package v4 import ( + "fmt" + "github.com/aws/amazon-ecs-agent/ecs-agent/logger" "github.com/aws/amazon-ecs-agent/ecs-agent/logger/field" tmdsv4 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v4/state" "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/utils/netconfig" ) +const ( + defaultNetworkInterfaceNameNotFoundError = "unable to obtain default network interface name on host from v3 endpoint ID: %s" +) + // Returns task metadata including the task network configuration in v4 format for the // task identified by the provided endpointContainerID. func (s *TMDSAgentState) GetTaskMetadataWithTaskNetworkConfig(v3EndpointID string, networkConfigClient *netconfig.NetworkConfigClient) (tmdsv4.TaskResponse, error) { taskResponse, err := s.getTaskMetadata(v3EndpointID, false, true) if err == nil { if taskResponse.TaskNetworkConfig != nil && taskResponse.TaskNetworkConfig.NetworkMode == "host" { - hostDeviceName, err := netconfig.DefaultNetInterfaceName(networkConfigClient.NetlinkClient) - if err != nil { - logger.Warn("Unable to obtain default network interface on host", logger.Fields{ + hostDeviceName, netErr := netconfig.DefaultNetInterfaceName(networkConfigClient.NetlinkClient) + if netErr != nil { + err = tmdsv4.NewErrorDefaultNetworkInterfaceName(fmt.Sprintf(defaultNetworkInterfaceNameNotFoundError, v3EndpointID)) + logger.Error("Unable to obtain default network interface on host", logger.Fields{ field.TaskARN: taskResponse.TaskARN, field.Error: err, }) diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/fault/v1/handlers/handlers.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/fault/v1/handlers/handlers.go index c966fcbe550..51a3156ea1d 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/fault/v1/handlers/handlers.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/fault/v1/handlers/handlers.go @@ -1115,12 +1115,29 @@ func validateTaskMetadata(w http.ResponseWriter, agentState state.AgentState, re func getTaskMetadataErrorResponse(endpointContainerID, requestType string, err error) (int, error) { var errContainerLookupFailed *state.ErrorLookupFailure if errors.As(err, &errContainerLookupFailed) { - return http.StatusNotFound, fmt.Errorf("unable to lookup container: %s", endpointContainerID) + logger.Error("Unable to lookup container", logger.Fields{ + field.Error: errContainerLookupFailed.ExternalReason(), + field.TMDSEndpointContainerID: endpointContainerID, + }) + return http.StatusNotFound, errors.New(errContainerLookupFailed.ExternalReason()) } var errFailedToGetContainerMetadata *state.ErrorMetadataFetchFailure if errors.As(err, &errFailedToGetContainerMetadata) { - return http.StatusInternalServerError, fmt.Errorf("unable to obtain container metadata for container: %s", endpointContainerID) + logger.Error("Unable to obtain container metadata for container", logger.Fields{ + field.Error: errFailedToGetContainerMetadata.ExternalReason(), + field.TMDSEndpointContainerID: endpointContainerID, + }) + return http.StatusInternalServerError, errors.New(errFailedToGetContainerMetadata.ExternalReason()) + } + + var errDefaultNetworkInterfaceName *state.ErrorDefaultNetworkInterfaceName + if errors.As(err, &errDefaultNetworkInterfaceName) { + logger.Error("Unable to obtain default network interface on host", logger.Fields{ + field.Error: errDefaultNetworkInterfaceName.ExternalReason(), + field.TMDSEndpointContainerID: endpointContainerID, + }) + return http.StatusInternalServerError, errors.New(errDefaultNetworkInterfaceName.ExternalReason()) } logger.Error("Unknown error encountered when handling task metadata fetch failure", logger.Fields{ diff --git a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v4/state/state.go b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v4/state/state.go index 04ade945bbb..5cd7b49226f 100644 --- a/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v4/state/state.go +++ b/agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v4/state/state.go @@ -94,6 +94,23 @@ func (e *ErrorStatsFetchFailure) Unwrap() error { return e.cause } +// Error to be returned when we're unable to obtain the default network interface name on the host namespace for a task +type ErrorDefaultNetworkInterfaceName struct { + externalReason string // Reason to be returned in TMDS response +} + +func NewErrorDefaultNetworkInterfaceName(externalReason string) *ErrorDefaultNetworkInterfaceName { + return &ErrorDefaultNetworkInterfaceName{externalReason: externalReason} +} + +func (e *ErrorDefaultNetworkInterfaceName) ExternalReason() string { + return e.externalReason +} + +func (e *ErrorDefaultNetworkInterfaceName) Error() string { + return fmt.Sprintf("failed to obtain default network interface name: %s", e.externalReason) +} + // Interface for interacting with Agent State relevant to TMDS type AgentState interface { // Returns container metadata in v4 format for the container identified by the diff --git a/ecs-agent/tmds/handlers/fault/v1/handlers/handlers.go b/ecs-agent/tmds/handlers/fault/v1/handlers/handlers.go index c966fcbe550..51a3156ea1d 100644 --- a/ecs-agent/tmds/handlers/fault/v1/handlers/handlers.go +++ b/ecs-agent/tmds/handlers/fault/v1/handlers/handlers.go @@ -1115,12 +1115,29 @@ func validateTaskMetadata(w http.ResponseWriter, agentState state.AgentState, re func getTaskMetadataErrorResponse(endpointContainerID, requestType string, err error) (int, error) { var errContainerLookupFailed *state.ErrorLookupFailure if errors.As(err, &errContainerLookupFailed) { - return http.StatusNotFound, fmt.Errorf("unable to lookup container: %s", endpointContainerID) + logger.Error("Unable to lookup container", logger.Fields{ + field.Error: errContainerLookupFailed.ExternalReason(), + field.TMDSEndpointContainerID: endpointContainerID, + }) + return http.StatusNotFound, errors.New(errContainerLookupFailed.ExternalReason()) } var errFailedToGetContainerMetadata *state.ErrorMetadataFetchFailure if errors.As(err, &errFailedToGetContainerMetadata) { - return http.StatusInternalServerError, fmt.Errorf("unable to obtain container metadata for container: %s", endpointContainerID) + logger.Error("Unable to obtain container metadata for container", logger.Fields{ + field.Error: errFailedToGetContainerMetadata.ExternalReason(), + field.TMDSEndpointContainerID: endpointContainerID, + }) + return http.StatusInternalServerError, errors.New(errFailedToGetContainerMetadata.ExternalReason()) + } + + var errDefaultNetworkInterfaceName *state.ErrorDefaultNetworkInterfaceName + if errors.As(err, &errDefaultNetworkInterfaceName) { + logger.Error("Unable to obtain default network interface on host", logger.Fields{ + field.Error: errDefaultNetworkInterfaceName.ExternalReason(), + field.TMDSEndpointContainerID: endpointContainerID, + }) + return http.StatusInternalServerError, errors.New(errDefaultNetworkInterfaceName.ExternalReason()) } logger.Error("Unknown error encountered when handling task metadata fetch failure", logger.Fields{ diff --git a/ecs-agent/tmds/handlers/fault/v1/handlers/handlers_test.go b/ecs-agent/tmds/handlers/fault/v1/handlers/handlers_test.go index cee8eb8f71b..2353fe9b91f 100644 --- a/ecs-agent/tmds/handlers/fault/v1/handlers/handlers_test.go +++ b/ecs-agent/tmds/handlers/fault/v1/handlers/handlers_test.go @@ -330,7 +330,7 @@ func generateCommonNetworkBlackHolePortTestCases(name string) []networkFaultInje name: fmt.Sprintf("%s task lookup fail", name), expectedStatusCode: 404, requestBody: happyBlackHolePortReqBody, - expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse(fmt.Sprintf("unable to lookup container: %s", endpointId)), + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("task lookup failed"), setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient). Return(state.TaskResponse{}, state.NewErrorLookupFailure("task lookup failed")). @@ -341,7 +341,7 @@ func generateCommonNetworkBlackHolePortTestCases(name string) []networkFaultInje name: fmt.Sprintf("%s task metadata fetch fail", name), expectedStatusCode: 500, requestBody: happyBlackHolePortReqBody, - expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse(fmt.Sprintf("unable to obtain container metadata for container: %s", endpointId)), + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("Unable to generate metadata for task"), setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient).Return(state.TaskResponse{}, state.NewErrorMetadataFetchFailure( "Unable to generate metadata for task")). @@ -478,6 +478,17 @@ func generateCommonNetworkBlackHolePortTestCases(name string) []networkFaultInje ) }, }, + { + name: fmt.Sprintf("%s task metadata obtain default network interface name fail", name), + expectedStatusCode: 500, + requestBody: happyBlackHolePortReqBody, + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("unable to obtain default network interface name on host"), + setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { + agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient).Return(state.TaskResponse{}, state.NewErrorDefaultNetworkInterfaceName( + "unable to obtain default network interface name on host")). + Times(1) + }, + }, } return tcs } @@ -1085,7 +1096,7 @@ func generateCommonNetworkLatencyTestCases(name string) []networkFaultInjectionT name: fmt.Sprintf("%s task lookup fail", name), expectedStatusCode: 404, requestBody: happyNetworkLatencyReqBody, - expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse(fmt.Sprintf("unable to lookup container: %s", endpointId)), + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("task lookup failed"), setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient).Return(state.TaskResponse{}, state.NewErrorLookupFailure("task lookup failed")) }, @@ -1094,7 +1105,7 @@ func generateCommonNetworkLatencyTestCases(name string) []networkFaultInjectionT name: fmt.Sprintf("%s task metadata fetch fail", name), expectedStatusCode: 500, requestBody: happyNetworkLatencyReqBody, - expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse(fmt.Sprintf("unable to obtain container metadata for container: %s", endpointId)), + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("Unable to generate metadata for task"), setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient).Return(state.TaskResponse{}, state.NewErrorMetadataFetchFailure( "Unable to generate metadata for task")) @@ -1193,6 +1204,16 @@ func generateCommonNetworkLatencyTestCases(name string) []networkFaultInjectionT ) }, }, + { + name: fmt.Sprintf("%s task metadata obtain default network interface name fail", name), + expectedStatusCode: 500, + requestBody: happyNetworkLatencyReqBody, + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("unable to obtain default network interface name on host"), + setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { + agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient).Return(state.TaskResponse{}, state.NewErrorDefaultNetworkInterfaceName( + "unable to obtain default network interface name on host")) + }, + }, } return tcs } @@ -1634,7 +1655,7 @@ func generateCommonNetworkPacketLossTestCases(name string) []networkFaultInjecti name: fmt.Sprintf("%s task lookup fail", name), expectedStatusCode: 404, requestBody: happyNetworkPacketLossReqBody, - expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse(fmt.Sprintf("unable to lookup container: %s", endpointId)), + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("task lookup failed"), setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient).Return(state.TaskResponse{}, state.NewErrorLookupFailure("task lookup failed")) }, @@ -1643,7 +1664,7 @@ func generateCommonNetworkPacketLossTestCases(name string) []networkFaultInjecti name: fmt.Sprintf("%s task metadata fetch fail", name), expectedStatusCode: 500, requestBody: happyNetworkPacketLossReqBody, - expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse(fmt.Sprintf("unable to obtain container metadata for container: %s", endpointId)), + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("Unable to generate metadata for task"), setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient).Return(state.TaskResponse{}, state.NewErrorMetadataFetchFailure( "Unable to generate metadata for task")) @@ -1742,6 +1763,16 @@ func generateCommonNetworkPacketLossTestCases(name string) []networkFaultInjecti ) }, }, + { + name: fmt.Sprintf("%s task metadata obtain default network interface name fail", name), + expectedStatusCode: 500, + requestBody: happyNetworkPacketLossReqBody, + expectedResponseBody: types.NewNetworkFaultInjectionErrorResponse("unable to obtain default network interface name on host"), + setAgentStateExpectations: func(agentState *mock_state.MockAgentState, netConfigClient *netconfig.NetworkConfigClient) { + agentState.EXPECT().GetTaskMetadataWithTaskNetworkConfig(endpointId, netConfigClient).Return(state.TaskResponse{}, state.NewErrorDefaultNetworkInterfaceName( + "unable to obtain default network interface name on host")) + }, + }, } return tcs } diff --git a/ecs-agent/tmds/handlers/v4/state/state.go b/ecs-agent/tmds/handlers/v4/state/state.go index 04ade945bbb..5cd7b49226f 100644 --- a/ecs-agent/tmds/handlers/v4/state/state.go +++ b/ecs-agent/tmds/handlers/v4/state/state.go @@ -94,6 +94,23 @@ func (e *ErrorStatsFetchFailure) Unwrap() error { return e.cause } +// Error to be returned when we're unable to obtain the default network interface name on the host namespace for a task +type ErrorDefaultNetworkInterfaceName struct { + externalReason string // Reason to be returned in TMDS response +} + +func NewErrorDefaultNetworkInterfaceName(externalReason string) *ErrorDefaultNetworkInterfaceName { + return &ErrorDefaultNetworkInterfaceName{externalReason: externalReason} +} + +func (e *ErrorDefaultNetworkInterfaceName) ExternalReason() string { + return e.externalReason +} + +func (e *ErrorDefaultNetworkInterfaceName) Error() string { + return fmt.Sprintf("failed to obtain default network interface name: %s", e.externalReason) +} + // Interface for interacting with Agent State relevant to TMDS type AgentState interface { // Returns container metadata in v4 format for the container identified by the