Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move v4 task metadata handler to ecs-agent module #3733

Merged
merged 8 commits into from
Jun 7, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add v4 task metadata handler to ecs-agent module
amogh09 committed Jun 2, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 89632df250acf63c1482257faae6d248c4a86f87
56 changes: 56 additions & 0 deletions ecs-agent/tmds/handlers/v4/handlers.go
Original file line number Diff line number Diff line change
@@ -37,6 +37,12 @@ func ContainerMetadataPath() string {
return "/v4/" + utils.ConstructMuxVar(EndpointContainerIDMuxName, utils.AnythingButSlashRegEx)
}

func TaskMetadataPath() string {
return fmt.Sprintf(
"/v4/%s/task",
utils.ConstructMuxVar(EndpointContainerIDMuxName, utils.AnythingButSlashRegEx))
}

// ContainerMetadataHandler returns the HTTP handler function for handling container metadata requests.
func ContainerMetadataHandler(
agentState state.AgentState,
@@ -86,3 +92,53 @@ func getContainerErrorResponse(endpointContainerID string, err error) (int, stri
logger.Fields{field.Error: err})
return http.StatusInternalServerError, "failed to get container metadata"
}

// TaskMetadataHandler returns the HTTP handler function for handling task metadata requests.
func TaskMetadataHandler(
agentState state.AgentState,
metricsFactory metrics.EntryFactory,
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
endpointContainerID := mux.Vars(r)[EndpointContainerIDMuxName]
taskMetadata, err := agentState.GetTaskMetadata(endpointContainerID)
if err != nil {
logger.Error("Failed to get v4 task metadata", logger.Fields{
field.TMDSEndpointContainerID: endpointContainerID,
field.Error: err,
})

responseCode, responseBody := getTaskErrorResponse(endpointContainerID, err)
utils.WriteJSONResponse(w, responseCode, responseBody, utils.RequestTypeTaskMetadata)

if utils.Is5XXStatus(responseCode) {
metricsFactory.New(metrics.InternalServerErrorMetricName).Done(err)()
}

return
}

logger.Info("Writing response for v4 task metadata", logger.Fields{
field.TMDSEndpointContainerID: endpointContainerID,
field.TaskARN: taskMetadata.TaskARN,
})
utils.WriteJSONResponse(w, http.StatusOK, taskMetadata, utils.RequestTypeContainerMetadata)
}
}

// Returns an appropriate HTTP response status code and body for the task metadata error.
func getTaskErrorResponse(endpointContainerID string, err error) (int, string) {
var errContainerLookupFailed *state.ErrorLookupFailure
if errors.As(err, &errContainerLookupFailed) {
return http.StatusNotFound, fmt.Sprintf("V4 task metadata handler: %s",
errContainerLookupFailed.ExternalReason())
}

var errFailedToGetContainerMetadata *state.ErrorMetadataFetchFailure
if errors.As(err, &errFailedToGetContainerMetadata) {
return http.StatusInternalServerError, errFailedToGetContainerMetadata.ExternalReason()
}

logger.Error("Unknown error encountered when handling task metadata fetch failure",
logger.Fields{field.Error: err})
return http.StatusInternalServerError, "failed to get task metadata"
}
110 changes: 109 additions & 1 deletion ecs-agent/tmds/handlers/v4/handlers_test.go
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ import (
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/aws/amazon-ecs-agent/ecs-agent/metrics"
mock_metrics "github.com/aws/amazon-ecs-agent/ecs-agent/metrics/mocks"
@@ -39,7 +40,12 @@ import (
)

const (
clusterName = "default"
taskARN = "taskARN"
family = "family"
endpointContainerID = "endpointContainerID"
vpcID = "vpcID"
availabilityzone = "availabilityZone"
containerID = "cid"
containerName = "sleepy"
imageName = "busybox"
@@ -101,6 +107,28 @@ var (
}},
},
}
now = time.Now()
taskResponse = state.TaskResponse{
TaskResponse: &v2.TaskResponse{
Cluster: clusterName,
TaskARN: taskARN,
Family: family,
Revision: version,
DesiredStatus: statusRunning,
KnownStatus: statusRunning,
Limits: &v2.LimitsResponse{
CPU: aws.Float64(cpu),
Memory: aws.Int64(memory),
},
PullStartedAt: aws.Time(now.UTC()),
PullStoppedAt: aws.Time(now.UTC()),
ExecutionStoppedAt: aws.Time(now.UTC()),
AvailabilityZone: availabilityzone,
LaunchType: "EC2",
},
Containers: []state.ContainerResponse{containerResponse},
VPCID: vpcID,
}
)

func TestContainerMetadata(t *testing.T) {
@@ -181,8 +209,88 @@ func TestContainerMetadata(t *testing.T) {
})
}

func TestTaskMetadata(t *testing.T) {
path := fmt.Sprintf("/v4/%s/task", endpointContainerID)

var setup = func(t *testing.T) (*mux.Router, *gomock.Controller, *mock_state.MockAgentState,
*mock_metrics.MockEntryFactory,
) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

agentState := mock_state.NewMockAgentState(ctrl)
metricsFactory := mock_metrics.NewMockEntryFactory(ctrl)

router := mux.NewRouter()
router.HandleFunc(
TaskMetadataPath(),
TaskMetadataHandler(agentState, metricsFactory))

return router, ctrl, agentState, metricsFactory
}

t.Run("happy case", func(t *testing.T) {
handler, _, agentState, _ := setup(t)
agentState.EXPECT().
GetTaskMetadata(endpointContainerID).
Return(taskResponse, nil)
testTMDSRequest(t, handler, TMDSTestCase[state.TaskResponse]{
path: path,
expectedStatusCode: http.StatusOK,
expectedResponseBody: taskResponse,
})
})
t.Run("task lookup failure", func(t *testing.T) {
handler, _, agentState, _ := setup(t)
agentState.EXPECT().
GetTaskMetadata(endpointContainerID).
Return(state.TaskResponse{}, state.NewErrorLookupFailure("task lookup failed"))
testTMDSRequest(t, handler, TMDSTestCase[string]{
path: path,
expectedStatusCode: http.StatusNotFound,
expectedResponseBody: "V4 task metadata handler: task lookup failed",
})
})
t.Run("metadata fetch failure", func(t *testing.T) {
handler, ctrl, agentState, metricsFactory := setup(t)

err := state.NewErrorMetadataFetchFailure(externalReason)
entry := mock_metrics.NewMockEntry(ctrl)

entry.EXPECT().Done(err).Return(func() {})
metricsFactory.EXPECT().New(metrics.InternalServerErrorMetricName).Return(entry)
agentState.EXPECT().
GetTaskMetadata(endpointContainerID).
Return(state.TaskResponse{}, state.NewErrorMetadataFetchFailure(externalReason))

testTMDSRequest(t, handler, TMDSTestCase[string]{
path: path,
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: externalReason,
})
})
t.Run("unknown error returned by AgentState", func(t *testing.T) {
handler, ctrl, agentState, metricsFactory := setup(t)

err := errors.New("unknown")
entry := mock_metrics.NewMockEntry(ctrl)

entry.EXPECT().Done(err).Return(func() {})
metricsFactory.EXPECT().New(metrics.InternalServerErrorMetricName).Return(entry)
agentState.EXPECT().
GetTaskMetadata(endpointContainerID).
Return(state.TaskResponse{}, err)

testTMDSRequest(t, handler, TMDSTestCase[string]{
path: path,
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: "failed to get task metadata",
})
})
}

type TMDSResponse interface {
string | state.ContainerResponse
string | state.ContainerResponse | state.TaskResponse
}

type TMDSTestCase[R TMDSResponse] struct {
15 changes: 15 additions & 0 deletions ecs-agent/tmds/handlers/v4/state/mocks/state_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions ecs-agent/tmds/handlers/v4/state/state.go
Original file line number Diff line number Diff line change
@@ -57,4 +57,9 @@ type AgentState interface {
// Returns ErrorLookupFailure if container lookup fails.
// Returns ErrorMetadataFetchFailure if something else goes wrong.
GetContainerMetadata(endpointContainerID string) (ContainerResponse, error)

// Returns task metadata in v4 format for the task identified by the provided endpointContainerID.
// Returns ErrorTaskLookupFailed if task lookup fails.
// Returns ErrorMetadataFetchFailure if something else goes wrong.
GetTaskMetadata(endpointContainerID string) (TaskResponse, error)
}