fix: Fix panic for non-existing metric names (#9757)

This commit is contained in:
Sven Rebhan 2021-09-15 19:58:40 +02:00 committed by GitHub
parent 0e9391d43f
commit c076398440
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 76 deletions

View File

@ -395,7 +395,7 @@ func NewXPathParserConfigs(metricName string, cfgs []XPathConfig) []xpath.Config
configs := make([]xpath.Config, 0, len(cfgs)) configs := make([]xpath.Config, 0, len(cfgs))
for _, cfg := range cfgs { for _, cfg := range cfgs {
config := xpath.Config(cfg) config := xpath.Config(cfg)
config.MetricName = metricName config.MetricDefaultName = metricName
configs = append(configs, config) configs = append(configs, config)
} }
return configs return configs

View File

@ -35,7 +35,7 @@ type Parser struct {
} }
type Config struct { type Config struct {
MetricName string MetricDefaultName string `toml:"-"`
MetricQuery string `toml:"metric_name"` MetricQuery string `toml:"metric_name"`
Selection string `toml:"metric_selection"` Selection string `toml:"metric_selection"`
Timestamp string `toml:"timestamp"` Timestamp string `toml:"timestamp"`
@ -160,13 +160,19 @@ func (p *Parser) parseQuery(starttime time.Time, doc, selected dataNode, config
// Determine the metric name. If a query was specified, use the result of this query and the default metric name // Determine the metric name. If a query was specified, use the result of this query and the default metric name
// otherwise. // otherwise.
metricname = config.MetricName metricname = config.MetricDefaultName
if len(config.MetricQuery) > 0 { if len(config.MetricQuery) > 0 {
v, err := p.executeQuery(doc, selected, config.MetricQuery) v, err := p.executeQuery(doc, selected, config.MetricQuery)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query metric name: %v", err) return nil, fmt.Errorf("failed to query metric name: %v", err)
} }
metricname = v.(string) var ok bool
if metricname, ok = v.(string); !ok {
if v == nil {
p.Log.Infof("Hint: Empty metric-name-node. If you wanted to set a constant please use `metric_name = \"'name'\"`.")
}
return nil, fmt.Errorf("failed to query metric name: query result is of type %T not 'string'", v)
}
} }
// By default take the time the parser was invoked and override the value // By default take the time the parser was invoked and override the value

View File

@ -148,7 +148,7 @@ func TestInvalidTypeQueriesFail(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
FieldsInt: map[string]string{ FieldsInt: map[string]string{
"a": "/Device_1/value_string", "a": "/Device_1/value_string",
@ -185,7 +185,7 @@ func TestInvalidTypeQueries(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{ Fields: map[string]string{
"a": "number(/Device_1/value_string)", "a": "number(/Device_1/value_string)",
@ -207,7 +207,7 @@ func TestInvalidTypeQueries(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{ Fields: map[string]string{
"a": "boolean(/Device_1/value_string)", "a": "boolean(/Device_1/value_string)",
@ -252,7 +252,7 @@ func TestParseTimestamps(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
}, },
}, },
@ -269,7 +269,7 @@ func TestParseTimestamps(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
TimestampFmt: "unix", TimestampFmt: "unix",
}, },
@ -287,7 +287,7 @@ func TestParseTimestamps(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix_ms", Timestamp: "/Device_1/Timestamp_unix_ms",
TimestampFmt: "unix_ms", TimestampFmt: "unix_ms",
}, },
@ -305,7 +305,7 @@ func TestParseTimestamps(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix_us", Timestamp: "/Device_1/Timestamp_unix_us",
TimestampFmt: "unix_us", TimestampFmt: "unix_us",
}, },
@ -323,7 +323,7 @@ func TestParseTimestamps(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix_ns", Timestamp: "/Device_1/Timestamp_unix_ns",
TimestampFmt: "unix_ns", TimestampFmt: "unix_ns",
}, },
@ -341,7 +341,7 @@ func TestParseTimestamps(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_iso", Timestamp: "/Device_1/Timestamp_iso",
TimestampFmt: "2006-01-02T15:04:05Z", TimestampFmt: "2006-01-02T15:04:05Z",
}, },
@ -382,7 +382,7 @@ func TestParseSingleValues(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{ Fields: map[string]string{
"a": "/Device_1/value_int", "a": "/Device_1/value_int",
@ -410,7 +410,7 @@ func TestParseSingleValues(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{ Fields: map[string]string{
"a": "number(Device_1/value_int)", "a": "number(Device_1/value_int)",
@ -438,7 +438,7 @@ func TestParseSingleValues(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{ Fields: map[string]string{
"b": "number(/Device_1/value_float)", "b": "number(/Device_1/value_float)",
@ -468,7 +468,7 @@ func TestParseSingleValues(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{ Fields: map[string]string{
"x": "substring-before(/Device_1/value_position, ';')", "x": "substring-before(/Device_1/value_position, ';')",
@ -492,7 +492,7 @@ func TestParseSingleValues(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{ Fields: map[string]string{
"x": "number(substring-before(/Device_1/value_position, ';'))", "x": "number(substring-before(/Device_1/value_position, ';'))",
@ -516,7 +516,7 @@ func TestParseSingleValues(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
FieldsInt: map[string]string{ FieldsInt: map[string]string{
"x": "substring-before(/Device_1/value_position, ';')", "x": "substring-before(/Device_1/value_position, ';')",
@ -540,7 +540,7 @@ func TestParseSingleValues(t *testing.T) {
input: singleMetricValuesXML, input: singleMetricValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Tags: map[string]string{ Tags: map[string]string{
"state": "/Device_1/State", "state": "/Device_1/State",
@ -587,7 +587,7 @@ func TestParseSingleAttributes(t *testing.T) {
input: singleMetricAttributesXML, input: singleMetricAttributesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix/@value", Timestamp: "/Device_1/Timestamp_unix/@value",
}, },
}, },
@ -604,7 +604,7 @@ func TestParseSingleAttributes(t *testing.T) {
input: singleMetricAttributesXML, input: singleMetricAttributesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_iso/@value", Timestamp: "/Device_1/Timestamp_iso/@value",
TimestampFmt: "2006-01-02T15:04:05Z", TimestampFmt: "2006-01-02T15:04:05Z",
}, },
@ -622,7 +622,7 @@ func TestParseSingleAttributes(t *testing.T) {
input: singleMetricAttributesXML, input: singleMetricAttributesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix/@value", Timestamp: "/Device_1/Timestamp_unix/@value",
Fields: map[string]string{ Fields: map[string]string{
"a": "/Device_1/attr_int/@_", "a": "/Device_1/attr_int/@_",
@ -650,7 +650,7 @@ func TestParseSingleAttributes(t *testing.T) {
input: singleMetricAttributesXML, input: singleMetricAttributesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix/@value", Timestamp: "/Device_1/Timestamp_unix/@value",
Fields: map[string]string{ Fields: map[string]string{
"a": "number(/Device_1/attr_int/@_)", "a": "number(/Device_1/attr_int/@_)",
@ -678,7 +678,7 @@ func TestParseSingleAttributes(t *testing.T) {
input: singleMetricAttributesXML, input: singleMetricAttributesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix/@value", Timestamp: "/Device_1/Timestamp_unix/@value",
Fields: map[string]string{ Fields: map[string]string{
"b": "number(/Device_1/attr_float/@_)", "b": "number(/Device_1/attr_float/@_)",
@ -708,7 +708,7 @@ func TestParseSingleAttributes(t *testing.T) {
input: singleMetricAttributesXML, input: singleMetricAttributesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix/@value", Timestamp: "/Device_1/Timestamp_unix/@value",
Fields: map[string]string{ Fields: map[string]string{
"name": "substring-after(/Device_1/Name/@value, ' ')", "name": "substring-after(/Device_1/Name/@value, ' ')",
@ -730,7 +730,7 @@ func TestParseSingleAttributes(t *testing.T) {
input: singleMetricAttributesXML, input: singleMetricAttributesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix/@value", Timestamp: "/Device_1/Timestamp_unix/@value",
Tags: map[string]string{ Tags: map[string]string{
"state": "/Device_1/State/@_", "state": "/Device_1/State/@_",
@ -754,7 +754,7 @@ func TestParseSingleAttributes(t *testing.T) {
input: singleMetricAttributesXML, input: singleMetricAttributesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Device_1/Timestamp_unix/@value", Timestamp: "/Device_1/Timestamp_unix/@value",
Fields: map[string]string{ Fields: map[string]string{
"a": "/Device_1/attr_bool_numeric/@_ = 1", "a": "/Device_1/attr_bool_numeric/@_ = 1",
@ -799,7 +799,7 @@ func TestParseMultiValues(t *testing.T) {
input: singleMetricMultiValuesXML, input: singleMetricMultiValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Timestamp/@value", Timestamp: "/Timestamp/@value",
Fields: map[string]string{ Fields: map[string]string{
"a": "number(/Device/Value[1])", "a": "number(/Device/Value[1])",
@ -831,7 +831,7 @@ func TestParseMultiValues(t *testing.T) {
input: singleMetricMultiValuesXML, input: singleMetricMultiValuesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Timestamp: "/Timestamp/@value", Timestamp: "/Timestamp/@value",
FieldsInt: map[string]string{ FieldsInt: map[string]string{
"a": "/Device/Value[1]", "a": "/Device/Value[1]",
@ -886,7 +886,7 @@ func TestParseMultiNodes(t *testing.T) {
input: multipleNodesXML, input: multipleNodesXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
Selection: "/Device", Selection: "/Device",
Timestamp: "/Timestamp/@value", Timestamp: "/Timestamp/@value",
Fields: map[string]string{ Fields: map[string]string{
@ -999,7 +999,7 @@ func TestParseMetricQuery(t *testing.T) {
input: metricNameQueryXML, input: metricNameQueryXML,
configs: []Config{ configs: []Config{
{ {
MetricName: "test", MetricDefaultName: "test",
MetricQuery: "name(/Device_1/Metric/@*[1])", MetricQuery: "name(/Device_1/Metric/@*[1])",
Timestamp: "/Device_1/Timestamp_unix", Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{ Fields: map[string]string{
@ -1017,6 +1017,29 @@ func TestParseMetricQuery(t *testing.T) {
time.Unix(1577923199, 0), time.Unix(1577923199, 0),
), ),
}, },
{
name: "parse metric name constant",
input: metricNameQueryXML,
configs: []Config{
{
MetricDefaultName: "test",
MetricQuery: "'the_metric'",
Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{
"value": "/Device_1/Metric/@*[1]",
},
},
},
defaultTags: map[string]string{},
expected: testutil.MustMetric(
"the_metric",
map[string]string{},
map[string]interface{}{
"value": "ok",
},
time.Unix(1577923199, 0),
),
},
} }
for _, tt := range tests { for _, tt := range tests {
@ -1032,6 +1055,42 @@ func TestParseMetricQuery(t *testing.T) {
} }
} }
func TestParseErrors(t *testing.T) {
var tests = []struct {
name string
input string
configs []Config
expected string
}{
{
name: "string metric name query",
input: metricNameQueryXML,
configs: []Config{
{
MetricDefaultName: "test",
MetricQuery: "arbitrary",
Timestamp: "/Device_1/Timestamp_unix",
Fields: map[string]string{
"value": "/Device_1/Metric/@*[1]",
},
},
},
expected: "failed to query metric name: query result is of type <nil> not 'string'",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parser := &Parser{Configs: tt.configs, DefaultTags: map[string]string{}, Log: testutil.Logger{Name: "parsers.xml"}}
require.NoError(t, parser.Init())
_, err := parser.ParseLine(tt.input)
require.Error(t, err)
require.Equal(t, tt.expected, err.Error())
})
}
}
func TestEmptySelection(t *testing.T) { func TestEmptySelection(t *testing.T) {
var tests = []struct { var tests = []struct {
name string name string
@ -1146,7 +1205,7 @@ func TestTestCases(t *testing.T) {
filename := filepath.FromSlash(tt.filename) filename := filepath.FromSlash(tt.filename)
cfg, header, err := loadTestConfiguration(filename) cfg, header, err := loadTestConfiguration(filename)
require.NoError(t, err) require.NoError(t, err)
cfg.MetricName = "xml" cfg.MetricDefaultName = "xml"
// Load the xml-content // Load the xml-content
input, err := testutil.ParseRawLinesFrom(header, "File:") input, err := testutil.ParseRawLinesFrom(header, "File:")