-
Notifications
You must be signed in to change notification settings - Fork 602
/
handlers.go
269 lines (231 loc) · 9.81 KB
/
handlers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// 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 v4
import (
"errors"
"fmt"
"net/http"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger/field"
"github.com/aws/amazon-ecs-agent/ecs-agent/metrics"
"github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/utils"
state "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v4/state"
"github.com/gorilla/mux"
)
// v3EndpointIDMuxName is the key that's used in gorilla/mux to get the v3 endpoint ID.
const (
EndpointContainerIDMuxName = "endpointContainerIDMuxName"
version = "v4"
containerStatsErrorPrefix = "V4 container stats handler"
taskStatsErrorPrefix = "V4 task stats handler"
)
// ContainerMetadataPath specifies the relative URI path for serving container metadata.
func ContainerMetadataPath() string {
return "/v4/" + utils.ConstructMuxVar(EndpointContainerIDMuxName, utils.AnythingButSlashRegEx)
}
// Returns the standard URI path for task metadata endpoint.
func TaskMetadataPath() string {
return fmt.Sprintf(
"/v4/%s/task",
utils.ConstructMuxVar(EndpointContainerIDMuxName, utils.AnythingButSlashRegEx))
}
// Returns the standard URI path for task metadata with tags endpoint.
func TaskMetadataWithTagsPath() string {
return fmt.Sprintf(
"/v4/%s/taskWithTags",
utils.ConstructMuxVar(EndpointContainerIDMuxName, utils.AnythingButSlashRegEx))
}
// Returns a standard URI path for v4 container stats endpoint.
func ContainerStatsPath() string {
return fmt.Sprintf("/v4/%s/stats",
utils.ConstructMuxVar(EndpointContainerIDMuxName, utils.AnythingButSlashRegEx))
}
// Returns a standard URI path for v4 task stats endpoint.
func TaskStatsPath() string {
return fmt.Sprintf("/v4/%s/task/stats",
utils.ConstructMuxVar(EndpointContainerIDMuxName, utils.AnythingButSlashRegEx))
}
// ContainerMetadataHandler returns the HTTP handler function for handling container metadata requests.
func ContainerMetadataHandler(
agentState state.AgentState,
metricsFactory metrics.EntryFactory,
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
endpointContainerID := mux.Vars(r)[EndpointContainerIDMuxName]
containerMetadata, err := agentState.GetContainerMetadata(endpointContainerID)
if err != nil {
logger.Error("Failed to get v4 container metadata", logger.Fields{
field.TMDSEndpointContainerID: endpointContainerID,
field.Error: err,
})
responseCode, responseBody := getContainerErrorResponse(endpointContainerID, err)
utils.WriteJSONResponse(w, responseCode, responseBody, utils.RequestTypeContainerMetadata)
if utils.Is5XXStatus(responseCode) {
metricsFactory.New(metrics.InternalServerErrorMetricName).Done(err)
}
return
}
logger.Info("Writing response for v4 container metadata", logger.Fields{
field.TMDSEndpointContainerID: endpointContainerID,
field.Container: containerMetadata.ID,
})
utils.WriteJSONResponse(w, http.StatusOK, containerMetadata, utils.RequestTypeContainerMetadata)
}
}
// Returns an appropriate HTTP response status code and body for the error.
func getContainerErrorResponse(endpointContainerID string, err error) (int, string) {
var errLookupFailure *state.ErrorLookupFailure
if errors.As(err, &errLookupFailure) {
return http.StatusNotFound, fmt.Sprintf("V4 container metadata handler: %s",
errLookupFailure.ExternalReason())
}
var errMetadataFetchFailure *state.ErrorMetadataFetchFailure
if errors.As(err, &errMetadataFetchFailure) {
return http.StatusInternalServerError, errMetadataFetchFailure.ExternalReason()
}
logger.Error("Unknown error encountered when handling container metadata fetch failure",
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 taskMetadataHandler(agentState, metricsFactory, false)
}
// TaskMetadataHandler returns the HTTP handler function for handling task metadata with tags requests.
func TaskMetadataWithTagsHandler(
agentState state.AgentState,
metricsFactory metrics.EntryFactory,
) func(http.ResponseWriter, *http.Request) {
return taskMetadataHandler(agentState, metricsFactory, true)
}
func taskMetadataHandler(
agentState state.AgentState,
metricsFactory metrics.EntryFactory,
includeTags bool,
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
endpointContainerID := mux.Vars(r)[EndpointContainerIDMuxName]
var taskMetadata state.TaskResponse
var err error
if includeTags {
taskMetadata, err = agentState.GetTaskMetadataWithTags(endpointContainerID)
} else {
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.RequestTypeTaskMetadata)
}
}
// 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"
}
// Returns an HTTP handler for v4 container stats endpoint
func ContainerStatsHandler(
agentState state.AgentState,
metricsFactory metrics.EntryFactory,
) func(http.ResponseWriter, *http.Request) {
return statsHandler(agentState.GetContainerStats, metricsFactory,
utils.RequestTypeContainerStats, containerStatsErrorPrefix)
}
// Returns an HTTP handler for v4 task stats endpoint
func TaskStatsHandler(
agentState state.AgentState,
metricsFactory metrics.EntryFactory,
) func(http.ResponseWriter, *http.Request) {
return statsHandler(agentState.GetTaskStats, metricsFactory,
utils.RequestTypeTaskStats, taskStatsErrorPrefix)
}
// Generic function that returns an HTTP handler for container or task stats endpoint
// depending on the parameters.
func statsHandler[R state.StatsResponse | map[string]*state.StatsResponse](
getStats func(string) (R, error), // container stats or task stats getter function
metricsFactory metrics.EntryFactory,
requestType string, // container stats or task stats request type
errorPrefix string,
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// Extract endpoint container ID
endpointContainerID := mux.Vars(r)[EndpointContainerIDMuxName]
// Get stats
stats, err := getStats(endpointContainerID)
if err != nil {
logger.Error("Failed to get v4 stats", logger.Fields{
field.TMDSEndpointContainerID: endpointContainerID,
field.Error: err,
field.RequestType: requestType,
})
responseCode, responseBody := getStatsErrorResponse(endpointContainerID, err, errorPrefix)
utils.WriteJSONResponse(w, responseCode, responseBody, requestType)
if utils.Is5XXStatus(responseCode) {
metricsFactory.New(metrics.InternalServerErrorMetricName).Done(err)
}
return
}
// Write stats response
logger.Info("Writing response for v4 stats", logger.Fields{
field.TMDSEndpointContainerID: endpointContainerID,
field.RequestType: requestType,
})
utils.WriteJSONResponse(w, http.StatusOK, stats, requestType)
}
}
// Returns appropriate HTTP status code and response body for stats endpoint error cases.
func getStatsErrorResponse(endpointContainerID string, err error, errorPrefix string) (int, string) {
// 404 if lookup failure
var errLookupFailure *state.ErrorStatsLookupFailure
if errors.As(err, &errLookupFailure) {
return http.StatusNotFound, fmt.Sprintf(
"%s: %s", errorPrefix, errLookupFailure.ExternalReason())
}
// 500 if any other known failure
var errStatsFetchFailure *state.ErrorStatsFetchFailure
if errors.As(err, &errStatsFetchFailure) {
return http.StatusInternalServerError, errStatsFetchFailure.ExternalReason()
}
// 500 if unknown failure
logger.Error("Unknown error encountered when handling stats fetch error", logger.Fields{
field.Error: err,
})
return http.StatusInternalServerError, "failed to get stats"
}