mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-12 06:24:10 +01:00
console: split graph.go into query.go and graph.go
We will introduce more query types.
This commit is contained in:
4
Makefile
4
Makefile
@@ -80,8 +80,8 @@ console/filter/parser.go: console/filter/parser.peg | $(PIGEON) ; $(info $(M) ge
|
||||
console/frontend/node_modules: console/frontend/package.json console/frontend/yarn.lock
|
||||
console/frontend/node_modules: ; $(info $(M) fetching node modules…)
|
||||
$Q yarn install --silent --frozen-lockfile --cwd console/frontend && touch $@
|
||||
console/frontend/data/fields.json: console/graph.go ; $(info $(M) generate list of selectable fields…)
|
||||
$Q sed -En -e 's/^\tgraphColumn([a-zA-Z]+)( .*|$$)/ "\1"/p' $< \
|
||||
console/frontend/data/fields.json: console/query.go ; $(info $(M) generate list of selectable fields…)
|
||||
$Q sed -En -e 's/^\tqueryColumn([a-zA-Z]+)( .*|$$)/ "\1"/p' $< \
|
||||
| sed -E -e '1i [' -e '$$ ! s/$$/,/' -e '$$a ]'> $@
|
||||
$Q test -s $@
|
||||
console/data/frontend: Makefile console/frontend/node_modules
|
||||
|
||||
123
console/graph.go
123
console/graph.go
@@ -1,7 +1,6 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"akvorado/common/helpers"
|
||||
"akvorado/console/filter"
|
||||
)
|
||||
|
||||
// graphQuery describes the input for the /graph endpoint.
|
||||
@@ -19,126 +17,9 @@ type graphQuery struct {
|
||||
Start time.Time `json:"start" binding:"required"`
|
||||
End time.Time `json:"end" binding:"required"`
|
||||
Points int `json:"points" binding:"required"` // minimum number of points
|
||||
Dimensions []graphColumn `json:"dimensions"` // group by ...
|
||||
Dimensions []queryColumn `json:"dimensions"` // group by ...
|
||||
Limit int `json:"limit"` // limit product of dimensions
|
||||
Filter graphFilter `json:"filter"` // where ...
|
||||
}
|
||||
|
||||
type graphColumn int
|
||||
|
||||
const (
|
||||
graphColumnExporterAddress graphColumn = iota + 1
|
||||
graphColumnExporterName
|
||||
graphColumnExporterGroup
|
||||
graphColumnSrcAS
|
||||
graphColumnSrcCountry
|
||||
graphColumnInIfName
|
||||
graphColumnInIfDescription
|
||||
graphColumnInIfSpeed
|
||||
graphColumnInIfConnectivity
|
||||
graphColumnInIfProvider
|
||||
graphColumnInIfBoundary
|
||||
graphColumnEType
|
||||
graphColumnProto
|
||||
graphColumnSrcPort
|
||||
graphColumnSrcAddr
|
||||
graphColumnDstAS
|
||||
graphColumnDstCountry
|
||||
graphColumnOutIfName
|
||||
graphColumnOutIfDescription
|
||||
graphColumnOutIfSpeed
|
||||
graphColumnOutIfConnectivity
|
||||
graphColumnOutIfProvider
|
||||
graphColumnOutIfBoundary
|
||||
graphColumnDstAddr
|
||||
graphColumnDstPort
|
||||
graphColumnForwardingStatus
|
||||
)
|
||||
|
||||
var graphColumnMap = helpers.NewBimap(map[graphColumn]string{
|
||||
graphColumnExporterAddress: "ExporterAddress",
|
||||
graphColumnExporterName: "ExporterName",
|
||||
graphColumnExporterGroup: "ExporterGroup",
|
||||
graphColumnSrcAddr: "SrcAddr",
|
||||
graphColumnDstAddr: "DstAddr",
|
||||
graphColumnSrcAS: "SrcAS",
|
||||
graphColumnDstAS: "DstAS",
|
||||
graphColumnSrcCountry: "SrcCountry",
|
||||
graphColumnDstCountry: "DstCountry",
|
||||
graphColumnInIfName: "InIfName",
|
||||
graphColumnOutIfName: "OutIfName",
|
||||
graphColumnInIfDescription: "InIfDescription",
|
||||
graphColumnOutIfDescription: "OutIfDescription",
|
||||
graphColumnInIfSpeed: "InIfSpeed",
|
||||
graphColumnOutIfSpeed: "OutIfSpeed",
|
||||
graphColumnInIfConnectivity: "InIfConnectivity",
|
||||
graphColumnOutIfConnectivity: "OutIfConnectivity",
|
||||
graphColumnInIfProvider: "InIfProvider",
|
||||
graphColumnOutIfProvider: "OutIfProvider",
|
||||
graphColumnInIfBoundary: "InIfBoundary",
|
||||
graphColumnOutIfBoundary: "OutIfBoundary",
|
||||
graphColumnEType: "EType",
|
||||
graphColumnProto: "Proto",
|
||||
graphColumnSrcPort: "SrcPort",
|
||||
graphColumnDstPort: "DstPort",
|
||||
graphColumnForwardingStatus: "ForwardingStatus",
|
||||
})
|
||||
|
||||
func (gc graphColumn) MarshalText() ([]byte, error) {
|
||||
got, ok := graphColumnMap.LoadValue(gc)
|
||||
if ok {
|
||||
return []byte(got), nil
|
||||
}
|
||||
return nil, errors.New("unknown field")
|
||||
}
|
||||
func (gc graphColumn) String() string {
|
||||
got, _ := graphColumnMap.LoadValue(gc)
|
||||
return got
|
||||
}
|
||||
func (gc *graphColumn) UnmarshalText(input []byte) error {
|
||||
got, ok := graphColumnMap.LoadKey(string(input))
|
||||
if ok {
|
||||
*gc = got
|
||||
return nil
|
||||
}
|
||||
return errors.New("unknown field")
|
||||
}
|
||||
|
||||
type graphFilter struct {
|
||||
filter string
|
||||
}
|
||||
|
||||
func (gf graphFilter) MarshalText() ([]byte, error) {
|
||||
return []byte(gf.filter), nil
|
||||
}
|
||||
func (gf *graphFilter) UnmarshalText(input []byte) error {
|
||||
got, err := filter.Parse("", input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse filter: %s", filter.HumanError(err))
|
||||
}
|
||||
*gf = graphFilter{got.(string)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// toSQLSelect transforms a column into an expression to use in SELECT
|
||||
func (gc graphColumn) toSQLSelect() string {
|
||||
var strValue string
|
||||
switch gc {
|
||||
case graphColumnExporterAddress, graphColumnSrcAddr, graphColumnDstAddr:
|
||||
strValue = fmt.Sprintf("IPv6NumToString(%s)", gc)
|
||||
case graphColumnSrcAS, graphColumnDstAS:
|
||||
strValue = fmt.Sprintf(`concat(toString(%s), ': ', dictGetOrDefault('asns', 'name', %s, '???'))`,
|
||||
gc, gc)
|
||||
case graphColumnEType:
|
||||
strValue = `if(EType = 0x800, 'IPv4', if(EType = 0x86dd, 'IPv6', '???'))`
|
||||
case graphColumnProto:
|
||||
strValue = `dictGetOrDefault('protocols', 'name', Proto, '???')`
|
||||
case graphColumnInIfSpeed, graphColumnOutIfSpeed, graphColumnSrcPort, graphColumnDstPort, graphColumnForwardingStatus:
|
||||
strValue = fmt.Sprintf("toString(%s)", gc)
|
||||
default:
|
||||
strValue = gc.String()
|
||||
}
|
||||
return strValue
|
||||
Filter queryFilter `json:"filter"` // where ...
|
||||
}
|
||||
|
||||
// graphQueryToSQL converts a graph query to an SQL request
|
||||
|
||||
@@ -19,41 +19,6 @@ import (
|
||||
"akvorado/common/reporter"
|
||||
)
|
||||
|
||||
func TestGraphColumnSQLSelect(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input graphColumn
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Input: graphColumnSrcAddr,
|
||||
Expected: `IPv6NumToString(SrcAddr)`,
|
||||
}, {
|
||||
Input: graphColumnDstAS,
|
||||
Expected: `concat(toString(DstAS), ': ', dictGetOrDefault('asns', 'name', DstAS, '???'))`,
|
||||
}, {
|
||||
Input: graphColumnProto,
|
||||
Expected: `dictGetOrDefault('protocols', 'name', Proto, '???')`,
|
||||
}, {
|
||||
Input: graphColumnEType,
|
||||
Expected: `if(EType = 0x800, 'IPv4', if(EType = 0x86dd, 'IPv6', '???'))`,
|
||||
}, {
|
||||
Input: graphColumnOutIfSpeed,
|
||||
Expected: `toString(OutIfSpeed)`,
|
||||
}, {
|
||||
Input: graphColumnExporterName,
|
||||
Expected: `ExporterName`,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Input.String(), func(t *testing.T) {
|
||||
got := tc.Input.toSQLSelect()
|
||||
if diff := helpers.Diff(got, tc.Expected); diff != "" {
|
||||
t.Errorf("toSQLWhere (-got, +want):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphQuerySQL(t *testing.T) {
|
||||
cases := []struct {
|
||||
Description string
|
||||
@@ -66,8 +31,8 @@ func TestGraphQuerySQL(t *testing.T) {
|
||||
Start: time.Date(2022, 04, 10, 15, 45, 10, 0, time.UTC),
|
||||
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||
Points: 100,
|
||||
Dimensions: []graphColumn{},
|
||||
Filter: graphFilter{},
|
||||
Dimensions: []queryColumn{},
|
||||
Filter: queryFilter{},
|
||||
},
|
||||
Expected: `
|
||||
WITH
|
||||
@@ -86,8 +51,8 @@ ORDER BY time`,
|
||||
Start: time.Date(2022, 04, 10, 15, 45, 10, 0, time.UTC),
|
||||
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||
Points: 100,
|
||||
Dimensions: []graphColumn{},
|
||||
Filter: graphFilter{"DstCountry = 'FR' AND SrcCountry = 'US'"},
|
||||
Dimensions: []queryColumn{},
|
||||
Filter: queryFilter{"DstCountry = 'FR' AND SrcCountry = 'US'"},
|
||||
},
|
||||
Expected: `
|
||||
WITH
|
||||
@@ -107,11 +72,11 @@ ORDER BY time`,
|
||||
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||
Points: 100,
|
||||
Limit: 20,
|
||||
Dimensions: []graphColumn{
|
||||
graphColumnExporterName,
|
||||
graphColumnInIfProvider,
|
||||
Dimensions: []queryColumn{
|
||||
queryColumnExporterName,
|
||||
queryColumnInIfProvider,
|
||||
},
|
||||
Filter: graphFilter{},
|
||||
Filter: queryFilter{},
|
||||
},
|
||||
Expected: `
|
||||
WITH
|
||||
@@ -229,11 +194,11 @@ func TestGraphHandler(t *testing.T) {
|
||||
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||
Points: 100,
|
||||
Limit: 20,
|
||||
Dimensions: []graphColumn{
|
||||
graphColumnExporterName,
|
||||
graphColumnInIfProvider,
|
||||
Dimensions: []queryColumn{
|
||||
queryColumnExporterName,
|
||||
queryColumnInIfProvider,
|
||||
},
|
||||
Filter: graphFilter{"DstCountry = 'FR' AND SrcCountry = 'US'"},
|
||||
Filter: queryFilter{"DstCountry = 'FR' AND SrcCountry = 'US'"},
|
||||
}
|
||||
payload := new(bytes.Buffer)
|
||||
err = json.NewEncoder(payload).Encode(input)
|
||||
|
||||
126
console/query.go
Normal file
126
console/query.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"akvorado/common/helpers"
|
||||
"akvorado/console/filter"
|
||||
)
|
||||
|
||||
type queryColumn int
|
||||
|
||||
const (
|
||||
queryColumnExporterAddress queryColumn = iota + 1
|
||||
queryColumnExporterName
|
||||
queryColumnExporterGroup
|
||||
queryColumnSrcAS
|
||||
queryColumnSrcCountry
|
||||
queryColumnInIfName
|
||||
queryColumnInIfDescription
|
||||
queryColumnInIfSpeed
|
||||
queryColumnInIfConnectivity
|
||||
queryColumnInIfProvider
|
||||
queryColumnInIfBoundary
|
||||
queryColumnEType
|
||||
queryColumnProto
|
||||
queryColumnSrcPort
|
||||
queryColumnSrcAddr
|
||||
queryColumnDstAS
|
||||
queryColumnDstCountry
|
||||
queryColumnOutIfName
|
||||
queryColumnOutIfDescription
|
||||
queryColumnOutIfSpeed
|
||||
queryColumnOutIfConnectivity
|
||||
queryColumnOutIfProvider
|
||||
queryColumnOutIfBoundary
|
||||
queryColumnDstAddr
|
||||
queryColumnDstPort
|
||||
queryColumnForwardingStatus
|
||||
)
|
||||
|
||||
var queryColumnMap = helpers.NewBimap(map[queryColumn]string{
|
||||
queryColumnExporterAddress: "ExporterAddress",
|
||||
queryColumnExporterName: "ExporterName",
|
||||
queryColumnExporterGroup: "ExporterGroup",
|
||||
queryColumnSrcAddr: "SrcAddr",
|
||||
queryColumnDstAddr: "DstAddr",
|
||||
queryColumnSrcAS: "SrcAS",
|
||||
queryColumnDstAS: "DstAS",
|
||||
queryColumnSrcCountry: "SrcCountry",
|
||||
queryColumnDstCountry: "DstCountry",
|
||||
queryColumnInIfName: "InIfName",
|
||||
queryColumnOutIfName: "OutIfName",
|
||||
queryColumnInIfDescription: "InIfDescription",
|
||||
queryColumnOutIfDescription: "OutIfDescription",
|
||||
queryColumnInIfSpeed: "InIfSpeed",
|
||||
queryColumnOutIfSpeed: "OutIfSpeed",
|
||||
queryColumnInIfConnectivity: "InIfConnectivity",
|
||||
queryColumnOutIfConnectivity: "OutIfConnectivity",
|
||||
queryColumnInIfProvider: "InIfProvider",
|
||||
queryColumnOutIfProvider: "OutIfProvider",
|
||||
queryColumnInIfBoundary: "InIfBoundary",
|
||||
queryColumnOutIfBoundary: "OutIfBoundary",
|
||||
queryColumnEType: "EType",
|
||||
queryColumnProto: "Proto",
|
||||
queryColumnSrcPort: "SrcPort",
|
||||
queryColumnDstPort: "DstPort",
|
||||
queryColumnForwardingStatus: "ForwardingStatus",
|
||||
})
|
||||
|
||||
func (gc queryColumn) MarshalText() ([]byte, error) {
|
||||
got, ok := queryColumnMap.LoadValue(gc)
|
||||
if ok {
|
||||
return []byte(got), nil
|
||||
}
|
||||
return nil, errors.New("unknown field")
|
||||
}
|
||||
func (gc queryColumn) String() string {
|
||||
got, _ := queryColumnMap.LoadValue(gc)
|
||||
return got
|
||||
}
|
||||
func (gc *queryColumn) UnmarshalText(input []byte) error {
|
||||
got, ok := queryColumnMap.LoadKey(string(input))
|
||||
if ok {
|
||||
*gc = got
|
||||
return nil
|
||||
}
|
||||
return errors.New("unknown field")
|
||||
}
|
||||
|
||||
type queryFilter struct {
|
||||
filter string
|
||||
}
|
||||
|
||||
func (gf queryFilter) MarshalText() ([]byte, error) {
|
||||
return []byte(gf.filter), nil
|
||||
}
|
||||
func (gf *queryFilter) UnmarshalText(input []byte) error {
|
||||
got, err := filter.Parse("", input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse filter: %s", filter.HumanError(err))
|
||||
}
|
||||
*gf = queryFilter{got.(string)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// toSQLSelect transforms a column into an expression to use in SELECT
|
||||
func (gc queryColumn) toSQLSelect() string {
|
||||
var strValue string
|
||||
switch gc {
|
||||
case queryColumnExporterAddress, queryColumnSrcAddr, queryColumnDstAddr:
|
||||
strValue = fmt.Sprintf("IPv6NumToString(%s)", gc)
|
||||
case queryColumnSrcAS, queryColumnDstAS:
|
||||
strValue = fmt.Sprintf(`concat(toString(%s), ': ', dictGetOrDefault('asns', 'name', %s, '???'))`,
|
||||
gc, gc)
|
||||
case queryColumnEType:
|
||||
strValue = `if(EType = 0x800, 'IPv4', if(EType = 0x86dd, 'IPv6', '???'))`
|
||||
case queryColumnProto:
|
||||
strValue = `dictGetOrDefault('protocols', 'name', Proto, '???')`
|
||||
case queryColumnInIfSpeed, queryColumnOutIfSpeed, queryColumnSrcPort, queryColumnDstPort, queryColumnForwardingStatus:
|
||||
strValue = fmt.Sprintf("toString(%s)", gc)
|
||||
default:
|
||||
strValue = gc.String()
|
||||
}
|
||||
return strValue
|
||||
}
|
||||
42
console/query_test.go
Normal file
42
console/query_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"akvorado/common/helpers"
|
||||
)
|
||||
|
||||
func TestQueryColumnSQLSelect(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input queryColumn
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Input: queryColumnSrcAddr,
|
||||
Expected: `IPv6NumToString(SrcAddr)`,
|
||||
}, {
|
||||
Input: queryColumnDstAS,
|
||||
Expected: `concat(toString(DstAS), ': ', dictGetOrDefault('asns', 'name', DstAS, '???'))`,
|
||||
}, {
|
||||
Input: queryColumnProto,
|
||||
Expected: `dictGetOrDefault('protocols', 'name', Proto, '???')`,
|
||||
}, {
|
||||
Input: queryColumnEType,
|
||||
Expected: `if(EType = 0x800, 'IPv4', if(EType = 0x86dd, 'IPv6', '???'))`,
|
||||
}, {
|
||||
Input: queryColumnOutIfSpeed,
|
||||
Expected: `toString(OutIfSpeed)`,
|
||||
}, {
|
||||
Input: queryColumnExporterName,
|
||||
Expected: `ExporterName`,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Input.String(), func(t *testing.T) {
|
||||
got := tc.Input.toSQLSelect()
|
||||
if diff := helpers.Diff(got, tc.Expected); diff != "" {
|
||||
t.Errorf("toSQLWhere (-got, +want):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user