Skip to content

Commit 02a6727

Browse files
AmudaPalanimarinatedpork
authored andcommitted
Add GetUsageByPrincipal method to usage package (#140)
* add GetUsageByPrincipalId * add GetUsageByPrincipalId * add GetUsageByPrincipalId * add GetUsageByPrincipalId * add GetUsageByPrincipalId * add functional test * add functional test * update changelog * update with pr comments * fix lint error * modify usage api pattern * add GetUsageByStartDateAndPrincipalID route * fix lint error * fix import * add get all usage * update router * fix lint error * fix lint error * fix route order * fix error handling * fix lint * fix error handling * fix functional test usage api error * add usage functional tests * fix error
1 parent 3e72454 commit 02a6727

File tree

9 files changed

+482
-154
lines changed

9 files changed

+482
-154
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
- Support `metadata` parameter in `/accounts` API endpoints
66
- Add `PUT /accounts/:id` endpoint
77
- Fixed bug where child account's DCEPrincipal role trusted itself rather than the master account
8+
- Add GetUsageByPrincipal
89
- Fix default `budget_notification_from_email` TF var (See #143)
910

11+
1012
## v0.23.0
1113

1214
- Added `/accounts?accountStatus=<status>` URL for querying accounts by status.

cmd/lambda/usage/get.go

+80-40
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,123 @@
11
package main
22

33
import (
4-
"context"
54
"encoding/json"
65
"fmt"
76
"log"
87
"net/http"
98
"strconv"
109
"time"
1110

12-
"github.com/Optum/dce/pkg/usage"
13-
1411
"github.com/Optum/dce/pkg/api/response"
15-
"github.com/aws/aws-lambda-go/events"
1612
)
1713

18-
type getController struct {
19-
Dao usage.DB
20-
}
14+
// GetUsageByStartDateAndEndDate - Returns a list of usage by startDate and endDate
15+
func GetUsageByStartDateAndEndDate(w http.ResponseWriter, r *http.Request) {
2116

22-
// Call - function to return usage for input date range
23-
func (controller getController) Call(ctx context.Context, req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
24-
// Fetch the usage records.
25-
i, err := strconv.ParseInt(req.QueryStringParameters["startDate"], 10, 64)
17+
i, err := strconv.ParseInt(r.FormValue(StartDateParam), 10, 64)
2618
if err != nil {
27-
errorMessage := fmt.Sprintf("Failed to parse usage start date: %s", err)
28-
log.Print(errorMessage)
29-
return response.CreateAPIErrorResponse(http.StatusBadRequest,
30-
response.CreateErrorResponse(
31-
"Invalid startDate", errorMessage)), nil
19+
errorMsg := fmt.Sprintf("Failed to parse usage start date: %s", err)
20+
log.Println(errorMsg)
21+
WriteRequestValidationError(w, errorMsg)
22+
return
3223
}
3324
startDate := time.Unix(i, 0)
3425

35-
j, err := strconv.ParseInt(req.QueryStringParameters["endDate"], 10, 64)
26+
j, err := strconv.ParseInt(r.FormValue(EndDateParam), 10, 64)
3627
if err != nil {
37-
errorMessage := fmt.Sprintf("Failed to parse usage end date: %s", err)
38-
log.Print(errorMessage)
39-
return response.CreateAPIErrorResponse(http.StatusBadRequest,
40-
response.CreateErrorResponse(
41-
"Invalid endDate", errorMessage)), nil
28+
errorMsg := fmt.Sprintf("Failed to parse usage end date: %s", err)
29+
log.Println(errorMsg)
30+
WriteRequestValidationError(w, errorMsg)
31+
return
4232
}
4333
endDate := time.Unix(j, 0)
4434

45-
usageRecords, err := controller.Dao.GetUsageByDateRange(startDate, endDate)
46-
35+
usageRecords, err := UsageSvc.GetUsageByDateRange(startDate, endDate)
4736
if err != nil {
48-
log.Printf("Error Getting usage records for startDate %d: %s", startDate.Unix(), err)
49-
return response.CreateAPIErrorResponse(http.StatusInternalServerError,
50-
response.CreateErrorResponse("ServerError",
51-
fmt.Sprintf("Failed to get usage records for start date %d",
52-
startDate.Unix()))), nil
37+
errMsg := fmt.Sprintf("Error getting usage for given start date %s and end date %s: %s", r.FormValue(StartDateParam), r.FormValue(EndDateParam), err.Error())
38+
log.Println(errMsg)
39+
response.ServerErrorWithResponse(errMsg)
40+
return
5341
}
5442

5543
// Serialize them for the JSON response.
56-
usageResponses := []*response.UsageResponse{}
44+
usageResponseItems := []*response.UsageResponse{}
5745

5846
for _, a := range usageRecords {
5947
usageRes := response.UsageResponse(*a)
6048
usageRes.StartDate = startDate.Unix()
6149
usageRes.EndDate = endDate.Unix()
6250
log.Printf("usage: %v", usageRes)
63-
usageResponses = append(usageResponses, &usageRes)
51+
usageResponseItems = append(usageResponseItems, &usageRes)
52+
}
53+
54+
outputResponseItems := SumCostAmountByPrincipalID(usageResponseItems)
55+
56+
json.NewEncoder(w).Encode(outputResponseItems)
57+
}
58+
59+
// GetUsageByStartDateAndPrincipalID - Returns a list of usage by principalID and starting from start date to current date
60+
func GetUsageByStartDateAndPrincipalID(w http.ResponseWriter, r *http.Request) {
61+
62+
i, err := strconv.ParseInt(r.FormValue(StartDateParam), 10, 64)
63+
if err != nil {
64+
errorMsg := fmt.Sprintf("Failed to parse usage start date: %s", err)
65+
log.Println(errorMsg)
66+
WriteRequestValidationError(w, errorMsg)
67+
return
68+
}
69+
startDate := time.Unix(i, 0)
70+
71+
principalID := r.FormValue(PrincipalIDParam)
72+
73+
usageRecords, err := UsageSvc.GetUsageByPrincipal(startDate, principalID)
74+
if err != nil {
75+
errMsg := fmt.Sprintf("Error getting usage for given start date %s and principalID %s: %s", r.FormValue(StartDateParam), principalID, err.Error())
76+
log.Println(errMsg)
77+
WriteServerErrorWithResponse(w, errMsg)
78+
return
79+
}
80+
81+
// Serialize them for the JSON response.
82+
usageResponseItems := []*response.UsageResponse{}
83+
84+
for _, a := range usageRecords {
85+
usageRes := response.UsageResponse(*a)
86+
usageResponseItems = append(usageResponseItems, &usageRes)
6487
}
6588

66-
outputResponses := SumCostAmountByPrincipalID(usageResponses)
89+
json.NewEncoder(w).Encode(usageResponseItems)
90+
}
91+
92+
// GetAllUsage - Returns a list of entire usage
93+
func GetAllUsage(w http.ResponseWriter, r *http.Request) {
6794

68-
messageBytes, err := json.Marshal(outputResponses)
95+
log.Printf("Get all usage request: %v", r)
96+
startDate := time.Now().AddDate(-1, 0, 0)
97+
endDate := time.Now()
6998

99+
usageRecords, err := UsageSvc.GetUsageByDateRange(startDate, endDate)
70100
if err != nil {
71-
errorMessage := fmt.Sprintf("Failed to serialize data: %s", err)
72-
log.Print(errorMessage)
73-
return response.CreateAPIErrorResponse(http.StatusInternalServerError,
74-
response.CreateErrorResponse(
75-
"ServerError", errorMessage)), nil
101+
errMsg := fmt.Sprintf("Error getting all usage : %s", err.Error())
102+
log.Println(errMsg)
103+
WriteServerErrorWithResponse(w, errMsg)
104+
return
105+
}
106+
107+
// Serialize them for the JSON response.
108+
usageResponseItems := []*response.UsageResponse{}
109+
110+
for _, a := range usageRecords {
111+
usageRes := response.UsageResponse(*a)
112+
usageRes.StartDate = startDate.Unix()
113+
usageRes.EndDate = endDate.Unix()
114+
log.Printf("usage: %v", usageRes)
115+
usageResponseItems = append(usageResponseItems, &usageRes)
76116
}
77117

78-
body := string(messageBytes)
118+
outputResponseItems := SumCostAmountByPrincipalID(usageResponseItems)
79119

80-
return response.CreateAPIResponse(http.StatusOK, body), nil
120+
json.NewEncoder(w).Encode(outputResponseItems)
81121
}
82122

83123
// SumCostAmountByPrincipalID returns a unique subset of the input slice by finding unique PrincipalIds and adding cost amount for it.

cmd/lambda/usage/main.go

+150-30
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,181 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
6-
"log"
77
"net/http"
8-
"strings"
8+
9+
"log"
910

1011
"github.com/Optum/dce/pkg/api"
11-
"github.com/Optum/dce/pkg/api/response"
12+
"github.com/Optum/dce/pkg/common"
1213
"github.com/Optum/dce/pkg/usage"
14+
"github.com/aws/aws-sdk-go/aws/session"
15+
16+
"github.com/Optum/dce/pkg/api/response"
1317
"github.com/aws/aws-lambda-go/events"
1418
"github.com/aws/aws-lambda-go/lambda"
19+
20+
"github.com/awslabs/aws-lambda-go-api-proxy/gorillamux"
21+
)
22+
23+
const (
24+
StartDateParam = "startDate"
25+
EndDateParam = "endDate"
26+
PrincipalIDParam = "principalId"
27+
AccountIDParam = "accountId"
1528
)
1629

17-
// Router structure holds all Controller instances for request
18-
type Router struct {
19-
GetController api.Controller
30+
var muxLambda *gorillamux.GorillaMuxAdapter
31+
32+
var (
33+
// Config - The configuration client
34+
Config common.DefaultEnvConfig
35+
// AWSSession - The AWS session
36+
AWSSession *session.Session
37+
38+
// UsageSvc - Service for getting usage
39+
UsageSvc *usage.DB
40+
)
41+
42+
// messageBody is the structured object of the JSON Message to send
43+
// to an SNS Topic for Provision and Decommission
44+
type messageBody struct {
45+
Default string `json:"default"`
46+
Body string `json:"Body"`
2047
}
2148

22-
func (router *Router) route(ctx context.Context, req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
23-
var res events.APIGatewayProxyResponse
24-
var err error
25-
switch {
26-
case req.HTTPMethod == http.MethodGet && strings.Contains(req.Path, "/usage"):
27-
res, err = router.GetController.Call(ctx, req)
28-
default:
29-
return response.NotFoundError(), nil
30-
}
49+
func init() {
50+
log.Println("Cold start; creating router for /usage")
3151

32-
// Handle errors returned by controllers
33-
if err != nil {
34-
log.Printf("Controller error: %s", err)
35-
return response.ServerErrorWithResponse(err.Error()), nil
52+
usageRoutes := api.Routes{
53+
54+
api.Route{
55+
"GetUsageByStartDateAndEndDate",
56+
"GET",
57+
"/usage",
58+
[]string{StartDateParam, EndDateParam},
59+
GetUsageByStartDateAndEndDate,
60+
},
61+
api.Route{
62+
"GetUsageByStartDateAndPrincipalID",
63+
"GET",
64+
"/usage",
65+
[]string{StartDateParam, PrincipalIDParam},
66+
GetUsageByStartDateAndPrincipalID,
67+
},
68+
api.Route{
69+
"GetAllUsage",
70+
"GET",
71+
"/usage",
72+
api.EmptyQueryString,
73+
GetAllUsage,
74+
},
3675
}
76+
r := api.NewRouter(usageRoutes)
77+
muxLambda = gorillamux.New(r)
78+
}
79+
80+
// Handler - Handle the lambda function
81+
func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
82+
// If no name is provided in the HTTP request body, throw an error
83+
return muxLambda.ProxyWithContext(ctx, req)
84+
}
3785

38-
return res, nil
86+
// buildBaseURL returns a base API url from the request properties.
87+
func buildBaseURL(r *http.Request) string {
88+
return r.URL.String()
3989
}
4090

4191
func main() {
42-
// Setup services
43-
usageSvc := newUsage()
4492

45-
// Configure the Router + Controllers
46-
router := &Router{
47-
GetController: getController{Dao: usageSvc},
48-
}
93+
AWSSession = newAWSSession()
94+
95+
UsageSvc = newUsage()
4996

50-
// Send Lambda requests to the router
51-
lambda.Start(router.route)
97+
lambda.Start(Handler)
5298
}
5399

54-
func newUsage() usage.DB {
100+
func newAWSSession() *session.Session {
101+
awsSession, err := session.NewSession()
102+
if err != nil {
103+
errorMessage := fmt.Sprintf("Failed to create AWS session: %s", err)
104+
log.Fatal(errorMessage)
105+
}
106+
return awsSession
107+
}
108+
109+
func newUsage() *usage.DB {
55110
usageSvc, err := usage.NewFromEnv()
56111
if err != nil {
57112
errorMessage := fmt.Sprintf("Failed to initialize usage service: %s", err)
58113
log.Fatal(errorMessage)
59114
}
60115

61-
return *usageSvc
116+
return usageSvc
117+
}
118+
119+
// WriteServerErrorWithResponse - Writes a server error with the specific message.
120+
func WriteServerErrorWithResponse(w http.ResponseWriter, message string) {
121+
WriteAPIErrorResponse(
122+
w,
123+
http.StatusInternalServerError,
124+
"ServerError",
125+
message,
126+
)
127+
}
128+
129+
// WriteAPIErrorResponse - Writes the error response out to the provided ResponseWriter
130+
func WriteAPIErrorResponse(w http.ResponseWriter, responseCode int,
131+
errCode string, errMessage string) {
132+
// Create the Error Response
133+
errResp := response.CreateErrorResponse(errCode, errMessage)
134+
apiResponse, err := json.Marshal(errResp)
135+
136+
// Should most likely not return an error since response.ErrorResponse
137+
// is structured to be json compatible
138+
if err != nil {
139+
log.Printf("Failed to Create Valid Error Response: %s", err)
140+
WriteAPIResponse(w, http.StatusInternalServerError, fmt.Sprintf(
141+
"{\"error\":\"Failed to Create Valid Error Response: %s\"", err))
142+
}
143+
144+
// Write an error
145+
WriteAPIResponse(w, responseCode, string(apiResponse))
146+
}
147+
148+
// WriteAPIResponse - Writes the response out to the provided ResponseWriter
149+
func WriteAPIResponse(w http.ResponseWriter, status int, body string) {
150+
w.WriteHeader(status)
151+
w.Write([]byte(body))
152+
}
153+
154+
// WriteAlreadyExistsError - Writes the already exists error.
155+
func WriteAlreadyExistsError(w http.ResponseWriter) {
156+
WriteAPIErrorResponse(
157+
w,
158+
http.StatusConflict,
159+
"AlreadyExistsError",
160+
"The requested resource cannot be created, as it conflicts with an existing resource",
161+
)
162+
}
163+
164+
// WriteRequestValidationError - Writes a request validate error with the given message.
165+
func WriteRequestValidationError(w http.ResponseWriter, message string) {
166+
WriteAPIErrorResponse(
167+
w,
168+
http.StatusBadRequest,
169+
"RequestValidationError",
170+
message,
171+
)
172+
}
173+
174+
// WriteNotFoundError - Writes a request validate error with the given message.
175+
func WriteNotFoundError(w http.ResponseWriter) {
176+
WriteAPIErrorResponse(
177+
w,
178+
http.StatusNotFound,
179+
"NotFound",
180+
"The requested resource could not be found.",
181+
)
62182
}

0 commit comments

Comments
 (0)