console: API validation for /graph/table-interval

Also add simple HTTP tests.
This commit is contained in:
Vincent Bernat
2024-03-25 14:49:48 +01:00
parent e41e64a3ac
commit 4eaaf977b7
4 changed files with 66 additions and 30 deletions

View File

@@ -13,15 +13,9 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/gin-gonic/gin"
"net/http"
"akvorado/common/helpers"
"akvorado/console/query" "akvorado/console/query"
) )
const defaultPointsNumber = 200
// flowsTable describe a consolidated or unconsolidated flows table. // flowsTable describe a consolidated or unconsolidated flows table.
type flowsTable struct { type flowsTable struct {
Name string Name string
@@ -123,11 +117,6 @@ type context struct {
ToStartOfInterval func(string) string ToStartOfInterval func(string) string
} }
type tableIntervalResult struct {
Table string `json:"table"`
Interval uint64 `json:"interval"`
}
// templateEscape escapes `{{` and `}}` from a string. In fact, only // templateEscape escapes `{{` and `}}` from a string. In fact, only
// the opening tag needs to be escaped. // the opening tag needs to be escaped.
func templateEscape(input string) string { func templateEscape(input string) string {
@@ -151,17 +140,6 @@ func templateContext(context inputContext) string {
return fmt.Sprintf("context `%s`", string(encoded)) return fmt.Sprintf("context `%s`", string(encoded))
} }
func (c *Component) getTableAndIntervalHandlerFunc(gc *gin.Context) {
input := inputContext{Points: defaultPointsNumber}
if err := gc.ShouldBindJSON(&input); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return
}
table, interval, _ := c.computeTableAndInterval(input)
gc.JSON(http.StatusOK, tableIntervalResult{Table: table, Interval: uint64(interval.Seconds())})
}
func (c *Component) contextFunc(inputStr string) context { func (c *Component) contextFunc(inputStr string) context {
var input inputContext var input inputContext
if err := json.Unmarshal([]byte(inputStr), &input); err != nil { if err := json.Unmarshal([]byte(inputStr), &input); err != nil {

View File

@@ -301,12 +301,12 @@ func TestFinalizeQuery(t *testing.T) {
} }
} }
func TestGetTableInterval(t *testing.T) { func TestComputeBestTableAndInterval(t *testing.T) {
cases := []struct { cases := []struct {
Description string Description string
Tables []flowsTable Tables []flowsTable
Context inputContext Context inputContext
Expected tableIntervalResult Expected tableIntervalOutput
}{ }{
{ {
Description: "simple query without additional tables", Description: "simple query without additional tables",
@@ -315,7 +315,7 @@ func TestGetTableInterval(t *testing.T) {
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 86400, Points: 86400,
}, },
Expected: tableIntervalResult{Table: "flows", Interval: 1}, Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, { }, {
Description: "query with main table", Description: "query with main table",
Context: inputContext{ Context: inputContext{
@@ -324,7 +324,7 @@ func TestGetTableInterval(t *testing.T) {
MainTableRequired: true, MainTableRequired: true,
Points: 86400, Points: 86400,
}, },
Expected: tableIntervalResult{Table: "flows", Interval: 1}, Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, { }, {
Description: "only flows table available", Description: "only flows table available",
Tables: []flowsTable{{"flows", 0, time.Date(2022, 3, 10, 15, 45, 10, 0, time.UTC)}}, Tables: []flowsTable{{"flows", 0, time.Date(2022, 3, 10, 15, 45, 10, 0, time.UTC)}},
@@ -333,7 +333,7 @@ func TestGetTableInterval(t *testing.T) {
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 86400, Points: 86400,
}, },
Expected: tableIntervalResult{Table: "flows", Interval: 1}, Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, { }, {
Description: "select flows table out of range", Description: "select flows table out of range",
Tables: []flowsTable{ Tables: []flowsTable{
@@ -345,7 +345,7 @@ func TestGetTableInterval(t *testing.T) {
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution, Points: 720, // 2-minute resolution,
}, },
Expected: tableIntervalResult{Table: "flows", Interval: 1}, Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, { }, {
Description: "select consolidated table with better resolution", Description: "select consolidated table with better resolution",
Tables: []flowsTable{ Tables: []flowsTable{
@@ -358,7 +358,7 @@ func TestGetTableInterval(t *testing.T) {
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution, Points: 720, // 2-minute resolution,
}, },
Expected: tableIntervalResult{Table: "flows_1m0s", Interval: 60}, Expected: tableIntervalOutput{Table: "flows_1m0s", Interval: 60},
}, },
} }
@@ -368,7 +368,7 @@ func TestGetTableInterval(t *testing.T) {
c.flowsTables = tc.Tables c.flowsTables = tc.Tables
table, interval, _ := c.computeTableAndInterval( table, interval, _ := c.computeTableAndInterval(
tc.Context) tc.Context)
got := tableIntervalResult{ got := tableIntervalOutput{
Table: table, Table: table,
Interval: uint64(interval.Seconds()), Interval: uint64(interval.Seconds()),
} }

View File

@@ -422,3 +422,29 @@ func (c *Component) graphLineHandlerFunc(gc *gin.Context) {
} }
gc.JSON(http.StatusOK, output) gc.JSON(http.StatusOK, output)
} }
type tableIntervalInput struct {
Start time.Time `json:"start" binding:"required"`
End time.Time `json:"end" binding:"required,gtfield=Start"`
Points uint `json:"points" binding:"required,min=5,max=2000"` // minimum number of points
}
type tableIntervalOutput struct {
Table string `json:"table"`
Interval uint64 `json:"interval"`
}
func (c *Component) getTableAndIntervalHandlerFunc(gc *gin.Context) {
var input tableIntervalInput
if err := gc.ShouldBindJSON(&input); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return
}
table, interval, _ := c.computeTableAndInterval(inputContext{
Points: input.Points,
Start: input.Start,
End: input.End,
})
gc.JSON(http.StatusOK, tableIntervalOutput{Table: table, Interval: uint64(interval.Seconds())})
}

View File

@@ -979,3 +979,35 @@ func TestGraphLineHandler(t *testing.T) {
}, },
}) })
} }
func TestGetTableInterval(t *testing.T) {
_, h, _, mockClock := NewMock(t, DefaultConfiguration())
mockClock.Set(time.Date(2022, 4, 12, 15, 45, 10, 0, time.UTC))
helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{
{
Description: "simple query",
URL: "/api/v0/console/graph/table-interval",
JSONInput: gin.H{
"start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
"end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
"points": 300,
},
JSONOutput: gin.H{
"table": "flows",
"interval": 1,
},
}, {
Description: "too many points",
URL: "/api/v0/console/graph/table-interval",
JSONInput: gin.H{
"start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
"end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
"points": 86400,
},
StatusCode: 400,
JSONOutput: gin.H{
"message": `Key: 'tableIntervalInput.Points' Error:Field validation for 'Points' failed on the 'max' tag`,
},
},
})
}