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

RHINENG-14146 Optimize Get Recommendations Query #282

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
53 changes: 39 additions & 14 deletions internal/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,72 @@ import (
"net/http/httptest"
"reflect"
"testing"
"time"

"github.com/labstack/echo/v4"
)

func TestMapQueryParameters(t *testing.T) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// setup
type tests struct {
name string
qinputs map[string]string
qoutputs map[string][]string
qoutputs map[string]interface{}
errmsg string
}

var all_tests = []tests{
now := time.Now().UTC().Truncate(time.Second)
firstOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
startTime := time.Date(2023, 3, 23, 0, 0, 0, 0, time.UTC)
endTime := time.Date(2023, 3, 24, 0, 0, 0, 0, time.UTC)
inclusiveEndTime := endTime.Add(24 * time.Hour)

all_tests := []tests{
{
name: "When start date and end date are not provided",
qinputs: map[string]string{"start_date": "", "end_date": ""},
qoutputs: map[string]interface{}{
"recommendation_sets.monitoring_end_time <= ?": now,
"recommendation_sets.monitoring_end_time >= ?": firstOfMonth,
},
errmsg: `The startTime should be 1st of current month. The endTime should the current time.`,
},
{
name: "When start date and end date are provided",
qinputs: map[string]string{"start_date": "2023-03-23", "end_date": "2023-03-24"},
qoutputs: map[string][]string{
"DATE(recommendation_sets.monitoring_end_time) <= ?": {"2023-03-24"},
"DATE(recommendation_sets.monitoring_end_time) >= ?": {"2023-03-23"},
qinputs: map[string]string{"start_date": startTime.Format("2006-01-02"), "end_date": endTime.Format("2006-01-02")},
qoutputs: map[string]interface{}{
"recommendation_sets.monitoring_end_time <= ?": inclusiveEndTime,
"recommendation_sets.monitoring_end_time >= ?": startTime,
},
errmsg: `The recommendation_sets.monitoring_end_time should be less than or equal to end date!
The recommendation_sets.monitoring_end_time should be greater than or equal to start date!`,
},
}

for _, tt := range all_tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

// Create a fresh request and recorder for each parallel test
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

for k, v := range tt.qinputs {
c.QueryParams().Add(k, v)
}
defer func() {
// Cleanup query params regardless of test result
for k := range c.QueryParams() {
delete(c.QueryParams(), k)
}
}()
result, _ := MapQueryParameters(c)
if reflect.DeepEqual(result, tt.qoutputs) != true {
t.Errorf("%s", tt.errmsg)
}
for k := range c.QueryParams() {
delete(c.QueryParams(), k)
}
})
}
}
97 changes: 46 additions & 51 deletions internal/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ func GetRecommendationSetList(c echo.Context) error {
OrgID := XRHID.Identity.OrgID
user_permissions := get_user_permissions(c)
handlerName := "recommendationset-list"
var unitChoices = make(map[string]string)
unitChoices := make(map[string]string)

cpuUnitParam := c.QueryParam("cpu-unit")
var cpuUnitOptions = map[string]bool{
cpuUnitOptions := map[string]bool{
"millicores": true,
"cores": true,
}
Expand All @@ -36,7 +36,7 @@ func GetRecommendationSetList(c echo.Context) error {
}

memoryUnitParam := c.QueryParam("memory-unit")
var memoryUnitOptions = map[string]bool{
memoryUnitOptions := map[string]bool{
"bytes": true,
"MiB": true,
"GiB": true,
Expand All @@ -60,7 +60,7 @@ func GetRecommendationSetList(c echo.Context) error {

orderBy = c.QueryParam("order_by")
if orderBy != "" {
var orderByOptions = map[string]string{
orderByOptions := map[string]string{
"cluster": "clusters.cluster_alias",
"workload_type": "workloads.workload_type",
"workload": "workloads.workload_name",
Expand Down Expand Up @@ -113,9 +113,15 @@ func GetRecommendationSetList(c echo.Context) error {
return c.JSON(http.StatusBadRequest, echo.Map{"status": "error", "message": err.Error()})
}
recommendationSet := model.RecommendationSet{}
recommendationSets, count, error := recommendationSet.GetRecommendationSets(OrgID, orderQuery, limit, offset, queryParams, user_permissions)
getOptions := model.GetRecommendationOptions{
OrderQuery: orderQuery,
Limit: limit,
Offset: offset,
QueryParams: queryParams,
}
recommendationSets, count, error := recommendationSet.GetRecommendationSet(OrgID, user_permissions, getOptions)
if error != nil {
log.Error("unable to fetch records from database", error)
log.Errorf("unable to fetch records from database; %v", error)
}

trueUnitsStr := c.QueryParam("true-units")
Expand All @@ -127,35 +133,25 @@ func GetRecommendationSetList(c echo.Context) error {
return c.JSON(http.StatusBadRequest, echo.Map{"status": "error", "message": "invalid value for true-units"})
}
}

setk8sUnits := !trueUnits

allRecommendations := []map[string]interface{}{}

for _, recommendation := range recommendationSets {
recommendationData := make(map[string]interface{})

recommendationData["id"] = recommendation.ID
recommendationData["source_id"] = recommendation.Workload.Cluster.SourceId
recommendationData["cluster_uuid"] = recommendation.Workload.Cluster.ClusterUUID
recommendationData["cluster_alias"] = recommendation.Workload.Cluster.ClusterAlias
recommendationData["project"] = recommendation.Workload.Namespace
recommendationData["workload_type"] = recommendation.Workload.WorkloadType
recommendationData["workload"] = recommendation.Workload.WorkloadName
recommendationData["container"] = recommendation.ContainerName
recommendationData["last_reported"] = recommendation.Workload.Cluster.LastReportedAtStr
recommendationData["recommendations"] = UpdateRecommendationJSON(handlerName, recommendation.ID, recommendation.Workload.Cluster.ClusterUUID, unitChoices, setk8sUnits, recommendation.Recommendations)
allRecommendations = append(allRecommendations, recommendationData)

for i := range recommendationSets {
recommendationSets[i].RecommendationsJSON = UpdateRecommendationJSON(
handlerName,
recommendationSets[i].ID,
recommendationSets[i].ClusterUUID,
unitChoices,
setk8sUnits,
recommendationSets[i].Recommendations,
)
}

interfaceSlice := make([]interface{}, len(allRecommendations))
for i, v := range allRecommendations {
interfaceSlice := make([]interface{}, len(recommendationSets))
for i, v := range recommendationSets {
interfaceSlice[i] = v
}
results := CollectionResponse(interfaceSlice, c.Request(), count, limit, offset)
return c.JSON(http.StatusOK, results)

}

func GetRecommendationSet(c echo.Context) error {
Expand All @@ -170,7 +166,7 @@ func GetRecommendationSet(c echo.Context) error {
return c.JSON(http.StatusBadRequest, echo.Map{"status": "error", "message": "bad recommendation_id"})
}

var unitChoices = make(map[string]string)
unitChoices := make(map[string]string)

trueUnitsStr := c.QueryParam("true-units")
var trueUnits bool
Expand All @@ -181,11 +177,10 @@ func GetRecommendationSet(c echo.Context) error {
return c.JSON(http.StatusBadRequest, echo.Map{"status": "error", "message": "invalid value for true-units"})
}
}

setk8sUnits := !trueUnits

cpuUnitParam := c.QueryParam("cpu-unit")
var cpuUnitOptions = map[string]bool{
cpuUnitOptions := map[string]bool{
"millicores": true,
"cores": true,
}
Expand All @@ -201,7 +196,7 @@ func GetRecommendationSet(c echo.Context) error {
}

memoryUnitParam := c.QueryParam("memory-unit")
var memoryUnitOptions = map[string]bool{
memoryUnitOptions := map[string]bool{
"bytes": true,
"MiB": true,
"GiB": true,
Expand All @@ -218,29 +213,29 @@ func GetRecommendationSet(c echo.Context) error {
}

recommendationSetVar := model.RecommendationSet{}
recommendationSet, error := recommendationSetVar.GetRecommendationSetByID(OrgID, RecommendationUUID.String(), user_permissions)

if error != nil {
log.Error("unable to fetch records from database", error)
getOptions := model.GetRecommendationOptions{
RecommendationID: RecommendationUUID.String(),
}
recommendationSetList, _, error := recommendationSetVar.GetRecommendationSet(OrgID, user_permissions, getOptions)

recommendationSlice := make(map[string]interface{})

if len(recommendationSet.Recommendations) != 0 {
recommendationSlice["id"] = recommendationSet.ID
recommendationSlice["source_id"] = recommendationSet.Workload.Cluster.SourceId
recommendationSlice["cluster_uuid"] = recommendationSet.Workload.Cluster.ClusterUUID
recommendationSlice["cluster_alias"] = recommendationSet.Workload.Cluster.ClusterAlias
recommendationSlice["project"] = recommendationSet.Workload.Namespace
recommendationSlice["workload_type"] = recommendationSet.Workload.WorkloadType
recommendationSlice["workload"] = recommendationSet.Workload.WorkloadName
recommendationSlice["container"] = recommendationSet.ContainerName
recommendationSlice["last_reported"] = recommendationSet.Workload.Cluster.LastReportedAtStr
recommendationSlice["recommendations"] = UpdateRecommendationJSON(handlerName, recommendationSet.ID, recommendationSet.Workload.Cluster.ClusterUUID, unitChoices, setk8sUnits, recommendationSet.Recommendations)
if error != nil {
log.Errorf("unable to fetch recommendation %s; error %v", RecommendationIDStr, error)
return c.JSON(http.StatusNotFound, echo.Map{"status": "error", "message": "unable to fetch recommendation"})
}

if len(recommendationSetList) == 1 {
recommendationSet := recommendationSetList[0]
recommendationSet.RecommendationsJSON = UpdateRecommendationJSON(
handlerName,
recommendationSet.ID,
recommendationSet.ClusterUUID,
unitChoices,
setk8sUnits,
recommendationSet.Recommendations)
return c.JSON(http.StatusOK, recommendationSet)
} else {
return c.JSON(http.StatusNotFound, echo.Map{"status": "not_found", "message": "recommendation not found"})
}

return c.JSON(http.StatusOK, recommendationSlice)

}

func GetAppStatus(c echo.Context) error {
Expand Down
6 changes: 4 additions & 2 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"net/http"
"time"

"github.com/labstack/echo-contrib/echoprometheus"
"github.com/labstack/echo/v4"
Expand Down Expand Up @@ -53,8 +54,9 @@ func StartAPIServer() {
v1.GET("/recommendations/openshift/:recommendation-id", GetRecommendationSet)

s := http.Server{
Addr: ":" + cfg.API_PORT, //local dev server
Handler: app,
Addr: ":" + cfg.API_PORT, // local dev server
Handler: app,
ReadHeaderTimeout: time.Duration(cfg.ReadHeaderTimeout) * time.Second,
}
if err := s.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
Expand Down
Loading
Loading