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

Update t128_graphql to support compound fields #71

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions plugins/inputs/t128_graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ The graphql input plugin collects data from a 128T instance via graphQL.
# status = "paths/status"
# other = "allRouters/nodes/other-field" # absolute path

## Optional. Like `extract_fields`, but for use when the data returned for a
## requested leaf is not a leaf itself. The data below the field will be produced
## as a JSON string in the field.
# [inputs.t128_graphql.extract_compound_fields]
# addresses = "paths/addresses"

## The tags for filtering data with the desired name as the key (left) and the graphQL
## query path as the value (right). The path can be relative to the entry point or an absolute
## path that does not diverge from the entry-point and does not contain graphQL arguments such
Expand Down Expand Up @@ -65,6 +71,7 @@ query {
isActive
status
deviceInterface
addresses
}
}
}
Expand Down Expand Up @@ -92,12 +99,18 @@ For the query above, an example graphQL response is:
{
"isActive": true,
"status": "DOWN",
"deviceInterface": "10"
"deviceInterface": "10",
"addresses": [
"192.168.1.1"
]
},
{
"isActive": true,
"status": "UP",
"deviceInterface": "11"
"deviceInterface": "11",
"addresses": [
"192.168.1.5"
]
}
],
"name": "fake"
Expand All @@ -116,6 +129,6 @@ For the query above, an example graphQL response is:
For the response above, the collector outputs:

```text
peer-paths,router-name=RTR_EAST_COMBO,device-interface=10,peer-name=fake other="foo",is-active=true,status="DOWN" 1617285085000000000
peer-paths,router-name=RTR_EAST_COMBO,device-interface=11,peer-name=fake other="foo",is-active=true,status="UP" 1617285085000000000
peer-paths,router-name=RTR_EAST_COMBO,device-interface=10,peer-name=fake other="foo",is-active=true,status="DOWN",addresses="[\"192.168.1.1\"]" 1617285085000000000
peer-paths,router-name=RTR_EAST_COMBO,device-interface=11,peer-name=fake other="foo",is-active=true,status="UP",addresses="[\"192.168.1.5\"]" 1617285085000000000
```
19 changes: 13 additions & 6 deletions plugins/inputs/t128_graphql/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@ import (
"strings"
)

//Config stores paths to fields, tags and predicates to be used by BuildQuery and ProcessResponse
// Config stores paths to fields, tags and predicates to be used by BuildQuery and ProcessResponse
type Config struct {
Predicates map[string]string
Fields map[string]string
Tags map[string]string
Predicates map[string]string
Fields map[string]string
CompoundFields map[string]string
Tags map[string]string
}

/*
LoadConfig converts a telegraf config into paths to predicates, fields and tags to be used by BuildQuery and ProcessResponse
Paths correspond to keys in the output to avoid collisions and because they are used as lookups in ProcessResponse

Args:

entryPoint example - "allRouters(name:'ComboEast')/nodes/nodes(name:'east-combo')/nodes/arp/nodes"
fieldsIn example - map[string]string{"test-field": "test-field"}
tagsIn example - map[string]string{"test-tag": "test-tag"}

Example:

For the example input above, LoadConfig() will produce the following Config

*Config{
Expand All @@ -36,6 +39,8 @@ func LoadConfig(
entryPoint string,
fieldsWithRelPath map[string]string,
fieldsWithAbsPath map[string]string,
compoundFieldsWithRelPath map[string]string,
compoundFieldsWithAbsPath map[string]string,
tagsWithRelPath map[string]string,
tagsWithAbsPath map[string]string,
) *Config {
Expand All @@ -59,6 +64,8 @@ func LoadConfig(
config.Predicates = predicates
config.Fields = formatPaths(fieldsWithRelPath, path)
addDataWithAbsPath(config.Fields, fieldsWithAbsPath)
config.CompoundFields = formatPaths(compoundFieldsWithRelPath, path)
addDataWithAbsPath(config.CompoundFields, compoundFieldsWithAbsPath)
config.Tags = formatPaths(tagsWithRelPath, path)
addDataWithAbsPath(config.Tags, tagsWithAbsPath)

Expand All @@ -72,7 +79,7 @@ func addDataWithAbsPath(currentTags map[string]string, tagsWithAbsPath map[strin
return currentTags
}

//needed because users configure tags & fields with paths starting at entry_point with "/" instead of "."
// needed because users configure tags & fields with paths starting at entry_point with "/" instead of "."
func formatPaths(items map[string]string, basePath string) map[string]string {
newMap := make(map[string]string)
replacer := strings.NewReplacer("/", ".")
Expand All @@ -82,7 +89,7 @@ func formatPaths(items map[string]string, basePath string) map[string]string {
return newMap
}

//needed to strip whitespace and to replace ' with \"
// needed to strip whitespace and to replace ' with \"
func formatPredicate(predicate string) string {
replacer := strings.NewReplacer(" ", "", "'", "\"")
return replacer.Replace(predicate)
Expand Down
41 changes: 32 additions & 9 deletions plugins/inputs/t128_graphql/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,37 @@ import (
)

var JSONPathFormationTestCases = []struct {
Name string
EntryPoint string
Fields map[string]string
FieldsWithAbsPath map[string]string
Tags map[string]string
TagsWithAbsPath map[string]string
ExpectedOutput *plugin.Config
Name string
EntryPoint string
Fields map[string]string
FieldsWithAbsPath map[string]string
CompoundFields map[string]string
CompoundFieldsWithAbsPath map[string]string
Tags map[string]string
TagsWithAbsPath map[string]string
ExpectedOutput *plugin.Config
}{
{
Name: "process simple input",
EntryPoint: "allRouters(name:'ComboEast')/nodes/nodes(name:'east-combo')/nodes/arp/nodes",
Fields: getTestFields(),
CompoundFields: getTestCompoundFields(),
Tags: getTestTags(),
ExpectedOutput: getTestConfigWithPredicates("(name:\"ComboEast\")", "(name:\"east-combo\")"),
},
{
Name: "process predicate with list",
EntryPoint: "allRouters(names:['wan','lan'])/nodes/nodes(name:'east-combo')/nodes/arp/nodes",
Fields: getTestFields(),
CompoundFields: getTestCompoundFields(),
Tags: getTestTags(),
ExpectedOutput: getTestConfigWithPredicates("(names:[\"wan\",\"lan\"])", "(name:\"east-combo\")"),
},
{
Name: "process multi-value predicates",
EntryPoint: "allRouters(names:['wan', 'lan'], key2:'value2')/nodes/nodes(name:'east-combo')/nodes/arp/nodes",
Fields: getTestFields(),
CompoundFields: getTestCompoundFields(),
Tags: getTestTags(),
ExpectedOutput: getTestConfigWithPredicates("(names:[\"wan\",\"lan\"],key2:\"value2\")", "(name:\"east-combo\")"),
},
Expand All @@ -47,6 +52,12 @@ var JSONPathFormationTestCases = []struct {
FieldsWithAbsPath: map[string]string{
"other-field": "allServices/nodes/other",
},
CompoundFields: map[string]string{
"compound-field": "compound-field",
},
CompoundFieldsWithAbsPath: map[string]string{
"other-compound-field": "allServices/nodes/other-compound-field",
},
Tags: getTestTags(),
TagsWithAbsPath: map[string]string{
"name": "allServices/nodes/name",
Expand All @@ -60,6 +71,10 @@ var JSONPathFormationTestCases = []struct {
".data.allServices.nodes.timeSeriesAnalytic.timestamp": "timestamp",
".data.allServices.nodes.other": "other-field",
},
CompoundFields: map[string]string{
".data.allServices.nodes.timeSeriesAnalytic.compound-field": "compound-field",
".data.allServices.nodes.other-compound-field": "other-compound-field",
},
Tags: map[string]string{
".data.allServices.nodes.timeSeriesAnalytic.test-tag": "test-tag",
".data.allServices.nodes.name": "name",
Expand All @@ -70,6 +85,7 @@ var JSONPathFormationTestCases = []struct {
Name: "process complex config",
EntryPoint: "allRouters(names:['wan', 'lan'], key2:'value2')/nodes/nodes(names:['east-combo', 'west-combo'])/nodes/arp/nodes",
Fields: getTestFields(),
CompoundFields: getTestCompoundFields(),
Tags: getTestTags(),
ExpectedOutput: getTestConfigWithPredicates("(names:[\"wan\",\"lan\"],key2:\"value2\")", "(names:[\"east-combo\",\"west-combo\"])"),
},
Expand All @@ -81,8 +97,9 @@ func getTestConfigWithPredicates(pred1 string, pred2 string) *plugin.Config {
".data.allRouters.$predicate": pred1,
".data.allRouters.nodes.nodes.$predicate": pred2,
},
Fields: map[string]string{".data.allRouters.nodes.nodes.nodes.arp.nodes.test-field": "test-field"},
Tags: map[string]string{".data.allRouters.nodes.nodes.nodes.arp.nodes.test-tag": "test-tag"},
Fields: map[string]string{".data.allRouters.nodes.nodes.nodes.arp.nodes.test-field": "test-field"},
CompoundFields: map[string]string{".data.allRouters.nodes.nodes.nodes.arp.nodes.test-compound-field": "test-compound-field"},
Tags: map[string]string{".data.allRouters.nodes.nodes.nodes.arp.nodes.test-tag": "test-tag"},
}
}

Expand All @@ -93,6 +110,8 @@ func TestT128GraphqlEntryPointParsing(t *testing.T) {
testCase.EntryPoint,
testCase.Fields,
testCase.FieldsWithAbsPath,
testCase.CompoundFields,
testCase.CompoundFieldsWithAbsPath,
testCase.Tags,
testCase.TagsWithAbsPath,
)
Expand All @@ -105,6 +124,10 @@ func getTestFields() map[string]string {
return map[string]string{"test-field": "test-field"}
}

func getTestCompoundFields() map[string]string {
return map[string]string{"test-compound-field": "test-compound-field"}
}

func getTestTags() map[string]string {
return map[string]string{"test-tag": "test-tag"}
}
7 changes: 5 additions & 2 deletions plugins/inputs/t128_graphql/query_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
BuildQuery first creates an intermediary query object that is traversed by buildQueryBody() in pre-order

Args:

entryPoint example - "allRouters(name:'ComboEast')/nodes/nodes(name:'combo-east')/nodes/arp/nodes"
fields example - map[string]string{"enabled": "enabled"}
tags example - map[string]string{
Expand All @@ -26,6 +27,7 @@ Args:
}

Example:

For the example input above, buildQueryObject() will produce the following query object

{
Expand Down Expand Up @@ -77,12 +79,13 @@ func BuildQuery(config *Config) string {
return query
}

//buildQueryBody creates an intermediary query object that is traversed by buildQueryBody
// buildQueryBody creates an intermediary query object that is traversed by buildQueryBody
func buildQueryObject(config *Config) *gabs.Container {
jsonObj := gabs.New()

addToQueryObj(jsonObj, config.Predicates)
addToQueryObj(jsonObj, config.Fields)
addToQueryObj(jsonObj, config.CompoundFields)
addToQueryObj(jsonObj, config.Tags)

return jsonObj
Expand All @@ -94,7 +97,7 @@ func addToQueryObj(jsonObj *gabs.Container, items map[string]string) {
}
}

//buildQueryBody builds the graphql query body by traversing jsonObj in pre-order and writing to the provided writer
// buildQueryBody builds the graphql query body by traversing jsonObj in pre-order and writing to the provided writer
func buildQueryBody(jsonObj *gabs.Container, w io.Writer) {
jsonChildren, err := jsonObj.ChildrenMap()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion plugins/inputs/t128_graphql/query_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

const (
ValidQuerySingleTag = "query {\nallRouters(name:\"ComboEast\"){\nnodes{\nnodes(name:\"east-combo\"){\nnodes{\narp{\nnodes{\ntest-field\ntest-tag}}}}}}}"
ValidQuerySingleTag = "query {\nallRouters(name:\"ComboEast\"){\nnodes{\nnodes(name:\"east-combo\"){\nnodes{\narp{\nnodes{\ntest-compound-field\ntest-field\ntest-tag}}}}}}}"
ValidQueryDoubleTag = "query {\nallRouters(name:\"ComboEast\"){\nnodes{\nnodes(name:\"east-combo\"){\nnodes{\narp{\nnodes{\ntest-field\ntest-tag-1\ntest-tag-2}}}}}}}"
ValidQueryDoubleField = "query {\nallRouters(name:\"ComboEast\"){\nnodes{\nnodes(name:\"east-combo\"){\nnodes{\narp{\nnodes{\ntest-field-1\ntest-field-2\ntest-tag}}}}}}}"
ValidQueryNestedTag = "query {\nallRouters(name:\"ComboEast\"){\nnodes{\nnodes(name:\"east-combo\"){\nnodes{\narp{\nnodes{\nstate{\ntest-tag-2}\ntest-field\ntest-tag-1}}}}}}}"
Expand Down
Loading
Loading