Skip to content

Commit

Permalink
Merge pull request #1335 from Bee-lee/clusters-view
Browse files Browse the repository at this point in the history
Adding aggregator part for OCP Advisor Clusters view (Milestone 2)
Bee-lee authored Dec 1, 2021
2 parents 00a7441 + 219cdb9 commit 2d85620
Showing 12 changed files with 424 additions and 20 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ require (
github.com/RedHatInsights/insights-content-service v0.0.0-20201009081018-083923779f00
github.com/RedHatInsights/insights-operator-utils v1.22.0
github.com/RedHatInsights/insights-results-aggregator-data v1.3.3
github.com/RedHatInsights/insights-results-types v1.2.0
github.com/RedHatInsights/insights-results-types v1.3.1
github.com/Shopify/sarama v1.27.1
github.com/deckarep/golang-set v1.7.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -55,8 +55,9 @@ github.com/RedHatInsights/insights-results-aggregator-data v1.3.1/go.mod h1:Ylo2
github.com/RedHatInsights/insights-results-aggregator-data v1.3.2/go.mod h1:E1UaB+IjJ/muxvMstVoqJrB82zVKNykjTtCiM3tMHoM=
github.com/RedHatInsights/insights-results-aggregator-data v1.3.3 h1:K6j+Sr+S0iUqFEfevl+MI0dEXn5AxFeJn9FTU5razWA=
github.com/RedHatInsights/insights-results-aggregator-data v1.3.3/go.mod h1:udHNC7lBxYnu9AqMahABqvuclCzWUWSkbacQbUaehfI=
github.com/RedHatInsights/insights-results-types v1.2.0 h1:tFaGQE2h/4OIhf8sI/lRwJ/Fh0soO7a47de9x4irIJs=
github.com/RedHatInsights/insights-results-types v1.2.0/go.mod h1:6VVdMTGU/BAS2cW0KrHAUiDyocpyKqpPpEyp6AJ1tk8=
github.com/RedHatInsights/insights-results-types v1.3.1 h1:dctLHdEHsZuK1R0TTFomkYJHIk6je2okbrmMBnHWvAQ=
github.com/RedHatInsights/insights-results-types v1.3.1/go.mod h1:6VVdMTGU/BAS2cW0KrHAUiDyocpyKqpPpEyp6AJ1tk8=
github.com/RedHatInsights/kafka-zerolog v0.0.0-20210304172207-928f026dc7ec h1:/msFfckx6EIj0rZncrMUfNixFvsLbOiRIe4J0AurhDo=
github.com/RedHatInsights/kafka-zerolog v0.0.0-20210304172207-928f026dc7ec/go.mod h1:HJul5oCsCRNiRlh/ayJDGdW3PzGlid/5aaQwJBn7was=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
96 changes: 96 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
@@ -1287,6 +1287,102 @@
}
}
},
"/clusters/organizations/{org_id}/users/{user_id}/recommendations": {
"post": {
"summary": "getClustersRecommendationsList retrieves all hitting recommendations for all clusters given in the POST body",
"operationId": "getClustersRecommendationsPost",
"description": "Recommendations will be retrieved based on the list of cluster IDs that is part of request body.",
"parameters": [
{
"name": "org_id",
"in": "path",
"description": "Organization ID represented as positive integer",
"required": true,
"schema": {
"type": "integer",
"format": "int64",
"minimum": 0
}
},
{
"name": "user_id",
"in": "path",
"required": true,
"description": "Numeric ID of the user. An example: `42`",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "List of cluster IDs. An example: `34c3ecc5-624a-49a5-bab8-4fdc5e51a266.",
"required": true,
"content": {
"application/json": {
"schema": {
}
}
}
},
"responses": {
"200": {
"description": "Map of clusters and a list of hitting recommendations for corresponding clusters",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"clusters": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"created_at": {
"type": "string",
"description": "The time of last analysis for this cluster."
},
"recommendations": {
"type": "array",
"items": {
"type": "string",
"description": "The array of recommendation IDs",
"example": [
"rule.module|ERROR_KEY",
"rule.another.module|SPECIFIC_KEY"
]
}
}
}
},
"example": {
"1234ecc5-624a-49a5-bab8-4fdc5e51a266": {
"created_at": "2021-09-07T15:50+00Z",
"recommendations": [
"rule.module1|ERROR_KEY1",
"rule.module2|ERROR_KEY2"
]
},
"5678ecc5-624a-49a5-bab8-4fdc5e51a266": {
"created_at": "2021-09-07T15:50+00Z",
"recommendations": [
"rule.module1|ERROR_KEY1",
"rule.module2|ERROR_KEY2"
]
}
}
},
"status": {
"type": "string",
"example": "ok"
}
}
}
}
}
}
}
}
},
"/rules/users/{userId}/disabled": {
"get": {
"summary": "Returns a list of rules disabled from current account",
3 changes: 3 additions & 0 deletions server/endpoints.go
Original file line number Diff line number Diff line change
@@ -81,6 +81,8 @@ const (

// RecommendationsListEndpoint receives a list of clusters in POST body and returns a list of all recommendations hitting for them
RecommendationsListEndpoint = "recommendations/organizations/{org_id}/users/{user_id}/list"
// ClustersRecommendationsListEndpoint receives a list of clusters in POST body and returns a list of clusters with lists of hitting recommendations
ClustersRecommendationsListEndpoint = "clusters/organizations/{org_id}/users/{user_id}/recommendations"

// Rating accepts a list of ratings in the request body and store them in the database for the given user
Rating = "rules/organizations/{org_id}/users/{user_id}/rating"
@@ -166,4 +168,5 @@ func (server *HTTPServer) addRuleEnableDisableEndpointsToRouter(router *mux.Rout
// are related to the Insights Advisor application
func (server *HTTPServer) addInsightsAdvisorEndpointsToRouter(router *mux.Router, apiPrefix string) {
router.HandleFunc(apiPrefix+RecommendationsListEndpoint, server.getRecommendations).Methods(http.MethodPost, http.MethodOptions)
router.HandleFunc(apiPrefix+ClustersRecommendationsListEndpoint, server.getClustersRecommendationsList).Methods(http.MethodPost, http.MethodOptions)
}
42 changes: 42 additions & 0 deletions server/reports.go
Original file line number Diff line number Diff line change
@@ -304,3 +304,45 @@ func (server *HTTPServer) getRecommendations(writer http.ResponseWriter, request
log.Error().Err(err).Msg(responseDataError)
}
}

// getClustersRecommendationsList retrieves all recommendations hitting for all clusters specified in the request body
func (server *HTTPServer) getClustersRecommendationsList(writer http.ResponseWriter, request *http.Request) {
tStart := time.Now()

userID, ok := readUserID(writer, request)
if !ok {
// everything has been handled
return
}
log.Info().Str(userIDstr, string(userID)).Msg("getClustersRecommendationsList")

orgID, ok := readOrgID(writer, request)
if !ok {
// everything has been handled
return
}
log.Info().Int(orgIDStr, int(orgID)).Msg("getClustersRecommendationsList")

var listOfClusters []string
err := json.NewDecoder(request.Body).Decode(&listOfClusters)
if err != nil {
handleServerError(writer, err)
return
}
log.Info().Msgf("getClustersRecommendationsList number of clusters: %d", len(listOfClusters))

clustersRecommendations, err := server.Storage.ReadClusterListRecommendations(listOfClusters, orgID)
if err != nil {
log.Error().Err(err).Msg("Errors retrieving recommendations")
handleServerError(writer, err)
return
}

log.Info().Uint32(orgIDStr, uint32(orgID)).Msgf(
"getClustersRecommendationsList took %s", time.Since(tStart),
)
err = responses.SendOK(writer, responses.BuildOkResponseWithData("clusters", clustersRecommendations))
if err != nil {
log.Error().Err(err).Msg(responseDataError)
}
}
80 changes: 80 additions & 0 deletions server/server_test.go
Original file line number Diff line number Diff line change
@@ -1443,3 +1443,83 @@ func TestServeInfoMap(t *testing.T) {
StatusCode: http.StatusOK,
})
}

func TestHTTPServer_ClustersRecommendationsListEndpoint_NoRecommendations(t *testing.T) {
mockStorage, closer := helpers.MustGetMockStorage(t, true)
defer closer()

err := mockStorage.WriteRecommendationsForCluster(
testdata.OrgID, testdata.ClusterName, testdata.Report0Rules,
)
helpers.FailOnError(t, err)

clusterList := []types.ClusterName{testdata.GetRandomClusterID()}
reqBody, _ := json.Marshal(clusterList)

helpers.AssertAPIRequest(t, mockStorage, nil, &helpers.APIRequest{
Method: http.MethodPost,
Endpoint: server.ClustersRecommendationsListEndpoint,
EndpointArgs: []interface{}{testdata.OrgID, testdata.UserID},
Body: reqBody,
}, &helpers.APIResponse{
StatusCode: http.StatusOK,
Body: `{"clusters":{},"status":"ok"}`,
})
}

func TestHTTPServer_ClustersRecommendationsListEndpoint_2Recs1Cluster(t *testing.T) {
mockStorage, closer := helpers.MustGetMockStorage(t, true)
defer closer()

err := mockStorage.WriteRecommendationsForCluster(
testdata.OrgID, testdata.ClusterName, testdata.Report2Rules,
)
helpers.FailOnError(t, err)

clusterList := []types.ClusterName{testdata.ClusterName}
reqBody, _ := json.Marshal(clusterList)

// can't check body directly because of variable created_at timestamp, contents are tested in storage tests
helpers.AssertAPIRequest(t, mockStorage, nil, &helpers.APIRequest{
Method: http.MethodPost,
Endpoint: server.RecommendationsListEndpoint,
EndpointArgs: []interface{}{testdata.OrgID, testdata.UserID},
Body: reqBody,
}, &helpers.APIResponse{
StatusCode: http.StatusOK,
})
}

func TestHTTPServer_ClustersRecommendationsListEndpoint_BadOrgIDBadRequest(t *testing.T) {
mockStorage, closer := helpers.MustGetMockStorage(t, true)
defer closer()

clusterList := []types.ClusterName{testdata.GetRandomClusterID()}
reqBody, _ := json.Marshal(clusterList)

helpers.AssertAPIRequest(t, mockStorage, nil, &helpers.APIRequest{
Method: http.MethodPost,
Endpoint: server.ClustersRecommendationsListEndpoint,
EndpointArgs: []interface{}{"string", testdata.UserID},
Body: reqBody,
}, &helpers.APIResponse{
StatusCode: http.StatusBadRequest,
})
}

func TestHTTPServer_ClustersRecommendationsListEndpoint_MissingClusterListBadRequest(t *testing.T) {
mockStorage, closer := helpers.MustGetMockStorage(t, true)
defer closer()

var clusterListBadType string
reqBody, _ := json.Marshal(clusterListBadType)

helpers.AssertAPIRequest(t, mockStorage, nil, &helpers.APIRequest{
Method: http.MethodPost,
Endpoint: server.ClustersRecommendationsListEndpoint,
EndpointArgs: []interface{}{testdata.OrgID, testdata.UserID},
Body: reqBody,
}, &helpers.APIResponse{
StatusCode: http.StatusBadRequest,
})
}
4 changes: 2 additions & 2 deletions storage/consts.go
Original file line number Diff line number Diff line change
@@ -25,10 +25,10 @@ const (
selectorsKey = "selectors"
// key for recommendations' creation time
createdAtKey = "created_at"

// closeStatementError error string
closeStatementError = "Unable to close statement"

// inClauseError when constructing IN clause fails
inClauseError = "error constructing WHERE IN clause"
// recommendationTimestampFormat represents the datetime format of the created_at of recommendation table
recommendationTimestampFormat = "2006-01-02 15:04:05.000000000+00:00"
)
5 changes: 3 additions & 2 deletions storage/export_test.go
Original file line number Diff line number Diff line change
@@ -35,8 +35,9 @@ import (
type SQLHooks = sqlHooks

const (
LogFormatterString = logFormatterString
SQLHooksKeyQueryBeginTime = sqlHooksKeyQueryBeginTime
LogFormatterString = logFormatterString
SQLHooksKeyQueryBeginTime = sqlHooksKeyQueryBeginTime
RecommendationTimestampFormat = recommendationTimestampFormat
)

var (
7 changes: 7 additions & 0 deletions storage/noop_storage.go
Original file line number Diff line number Diff line change
@@ -329,3 +329,10 @@ func (*NoopStorage) ListOfClustersForOrgSpecificRule(
) ([]ctypes.HittingClustersData, error) {
return nil, nil
}

// ReadClusterListRecommendations retrieves cluster IDs and a list of hitting rules for each one
func (*NoopStorage) ReadClusterListRecommendations(
clusterList []string, orgID types.OrgID,
) (ctypes.ClusterRecommendationMap, error) {
return nil, nil
}
2 changes: 2 additions & 0 deletions storage/noop_storage_test.go
Original file line number Diff line number Diff line change
@@ -90,4 +90,6 @@ func TestNoopStorage_Methods_Cont2(t *testing.T) {
_, _ = noopStorage.ListOfSystemWideDisabledRules(orgID, userID)
_, _ = noopStorage.ListOfClustersForOrgSpecificRule(0, "", nil)
_, _ = noopStorage.ListOfClustersForOrgSpecificRule(0, "", []string{"a"})
_, _ = noopStorage.ReadRecommendationsForClusters([]string{}, types.OrgID(1))
_, _ = noopStorage.ReadClusterListRecommendations([]string{}, types.OrgID(1))
}
Loading

0 comments on commit 2d85620

Please sign in to comment.