891 lines
22 KiB
Go
891 lines
22 KiB
Go
|
package input
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/gopcua/opcua/ua"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/config"
|
||
|
"github.com/influxdata/telegraf/metric"
|
||
|
"github.com/influxdata/telegraf/plugins/common/opcua"
|
||
|
"github.com/influxdata/telegraf/testutil"
|
||
|
)
|
||
|
|
||
|
func TestTagsSliceToMap(t *testing.T) {
|
||
|
m, err := tagsSliceToMap([][]string{{"foo", "bar"}, {"baz", "bat"}})
|
||
|
require.NoError(t, err)
|
||
|
require.Len(t, m, 2)
|
||
|
require.Equal(t, "bar", m["foo"])
|
||
|
require.Equal(t, "bat", m["baz"])
|
||
|
}
|
||
|
|
||
|
func TestTagsSliceToMap_twoStrings(t *testing.T) {
|
||
|
var err error
|
||
|
_, err = tagsSliceToMap([][]string{{"foo", "bar", "baz"}})
|
||
|
require.Error(t, err)
|
||
|
_, err = tagsSliceToMap([][]string{{"foo"}})
|
||
|
require.Error(t, err)
|
||
|
}
|
||
|
|
||
|
func TestTagsSliceToMap_dupeKey(t *testing.T) {
|
||
|
_, err := tagsSliceToMap([][]string{{"foo", "bar"}, {"foo", "bat"}})
|
||
|
require.Error(t, err)
|
||
|
}
|
||
|
|
||
|
func TestTagsSliceToMap_empty(t *testing.T) {
|
||
|
_, err := tagsSliceToMap([][]string{{"foo", ""}})
|
||
|
require.Equal(t, errors.New("tag 1 has empty value"), err)
|
||
|
_, err = tagsSliceToMap([][]string{{"", "bar"}})
|
||
|
require.Equal(t, errors.New("tag 1 has empty name"), err)
|
||
|
}
|
||
|
|
||
|
func TestValidateOPCTags(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
config InputClientConfig
|
||
|
err error
|
||
|
}{
|
||
|
{
|
||
|
"duplicates",
|
||
|
InputClientConfig{
|
||
|
MetricName: "mn",
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t2", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
Groups: []NodeGroupSettings{
|
||
|
{
|
||
|
Nodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
},
|
||
|
},
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t2", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
errors.New(`name "fn" is duplicated (metric name "mn", tags "t1=v1, t2=v2")`),
|
||
|
},
|
||
|
{
|
||
|
"empty tag value not allowed",
|
||
|
InputClientConfig{
|
||
|
MetricName: "mn",
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
IdentifierType: "s",
|
||
|
TagsSlice: [][]string{{"t1", ""}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
errors.New("tag 1 has empty value"),
|
||
|
},
|
||
|
{
|
||
|
"empty tag name not allowed",
|
||
|
InputClientConfig{
|
||
|
MetricName: "mn",
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
IdentifierType: "s",
|
||
|
TagsSlice: [][]string{{"", "1"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
errors.New("tag 1 has empty name"),
|
||
|
},
|
||
|
{
|
||
|
"different metric tag names",
|
||
|
InputClientConfig{
|
||
|
MetricName: "mn",
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t2", "v2"}},
|
||
|
},
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t3", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
nil,
|
||
|
},
|
||
|
{
|
||
|
"different metric tag values",
|
||
|
InputClientConfig{
|
||
|
MetricName: "mn",
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "foo"}, {"t2", "v2"}},
|
||
|
},
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "bar"}, {"t2", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
nil,
|
||
|
},
|
||
|
{
|
||
|
"different metric names",
|
||
|
InputClientConfig{
|
||
|
MetricName: "mn",
|
||
|
Groups: []NodeGroupSettings{
|
||
|
{
|
||
|
MetricName: "mn",
|
||
|
Namespace: "2",
|
||
|
Nodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t2", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
MetricName: "mn2",
|
||
|
Namespace: "2",
|
||
|
Nodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t2", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
nil,
|
||
|
},
|
||
|
{
|
||
|
"different field names",
|
||
|
InputClientConfig{
|
||
|
MetricName: "mn",
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "fn",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t2", "v2"}},
|
||
|
},
|
||
|
{
|
||
|
FieldName: "fn2",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "i1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t2", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
nil,
|
||
|
},
|
||
|
}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
o := OpcUAInputClient{
|
||
|
Config: tt.config,
|
||
|
Log: testutil.Logger{},
|
||
|
}
|
||
|
require.Equal(t, tt.err, o.InitNodeMetricMapping())
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNewNodeMetricMappingTags(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
settings NodeSettings
|
||
|
groupTags map[string]string
|
||
|
expectedTags map[string]string
|
||
|
err error
|
||
|
}{
|
||
|
{
|
||
|
name: "empty tags",
|
||
|
settings: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "h",
|
||
|
},
|
||
|
groupTags: map[string]string{},
|
||
|
expectedTags: map[string]string{},
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
name: "node tags only",
|
||
|
settings: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "h",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}},
|
||
|
},
|
||
|
groupTags: map[string]string{},
|
||
|
expectedTags: map[string]string{"t1": "v1"},
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
name: "group tags only",
|
||
|
settings: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "h",
|
||
|
},
|
||
|
groupTags: map[string]string{"t1": "v1"},
|
||
|
expectedTags: map[string]string{"t1": "v1"},
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
name: "node tag overrides group tags",
|
||
|
settings: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "h",
|
||
|
TagsSlice: [][]string{{"t1", "v2"}},
|
||
|
},
|
||
|
groupTags: map[string]string{"t1": "v1"},
|
||
|
expectedTags: map[string]string{"t1": "v2"},
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
name: "node tag merged with group tags",
|
||
|
settings: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "h",
|
||
|
TagsSlice: [][]string{{"t2", "v2"}},
|
||
|
},
|
||
|
groupTags: map[string]string{"t1": "v1"},
|
||
|
expectedTags: map[string]string{"t1": "v1", "t2": "v2"},
|
||
|
err: nil,
|
||
|
},
|
||
|
}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", tt.settings, tt.groupTags)
|
||
|
require.Equal(t, tt.err, err)
|
||
|
require.Equal(t, tt.expectedTags, nmm.MetricTags)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNewNodeMetricMappingIdStrInstantiated(t *testing.T) {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "h",
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, "ns=2;s=h", nmm.idStr)
|
||
|
}
|
||
|
|
||
|
func TestValidateNodeToAdd(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
existing map[metricParts]struct{}
|
||
|
nmm *NodeMetricMapping
|
||
|
err error
|
||
|
}{
|
||
|
{
|
||
|
name: "valid",
|
||
|
existing: map[metricParts]struct{}{},
|
||
|
nmm: func() *NodeMetricMapping {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "hf",
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
return nmm
|
||
|
}(),
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
name: "empty field name not allowed",
|
||
|
existing: map[metricParts]struct{}{},
|
||
|
nmm: func() *NodeMetricMapping {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "hf",
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
return nmm
|
||
|
}(),
|
||
|
err: errors.New(`empty name in ""`),
|
||
|
},
|
||
|
{
|
||
|
name: "empty namespace not allowed",
|
||
|
existing: map[metricParts]struct{}{},
|
||
|
nmm: func() *NodeMetricMapping {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "hf",
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
return nmm
|
||
|
}(),
|
||
|
err: errors.New("empty node namespace not allowed"),
|
||
|
},
|
||
|
{
|
||
|
name: "empty identifier type not allowed",
|
||
|
existing: map[metricParts]struct{}{},
|
||
|
nmm: func() *NodeMetricMapping {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "",
|
||
|
Identifier: "hf",
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
return nmm
|
||
|
}(),
|
||
|
err: errors.New(`invalid identifier type "" in "f"`),
|
||
|
},
|
||
|
{
|
||
|
name: "invalid identifier type not allowed",
|
||
|
existing: map[metricParts]struct{}{},
|
||
|
nmm: func() *NodeMetricMapping {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "j",
|
||
|
Identifier: "hf",
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
return nmm
|
||
|
}(),
|
||
|
err: errors.New(`invalid identifier type "j" in "f"`),
|
||
|
},
|
||
|
{
|
||
|
name: "duplicate metric not allowed",
|
||
|
existing: map[metricParts]struct{}{
|
||
|
{metricName: "testmetric", fieldName: "f", tags: "t1=v1, t2=v2"}: {},
|
||
|
},
|
||
|
nmm: func() *NodeMetricMapping {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "hf",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}, {"t2", "v2"}},
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
return nmm
|
||
|
}(),
|
||
|
err: errors.New(`name "f" is duplicated (metric name "testmetric", tags "t1=v1, t2=v2")`),
|
||
|
},
|
||
|
{
|
||
|
name: "identifier type mismatch",
|
||
|
existing: map[metricParts]struct{}{},
|
||
|
nmm: func() *NodeMetricMapping {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "i",
|
||
|
Identifier: "hf",
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
return nmm
|
||
|
}(),
|
||
|
err: errors.New(`identifier type "i" does not match the type of identifier "hf"`),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for idT, idV := range map[string]string{
|
||
|
"s": "hf",
|
||
|
"i": "1",
|
||
|
"g": "849683f0-ce92-4fa2-836f-a02cde61d75d",
|
||
|
"b": "aGVsbG8gSSBhbSBhIHRlc3QgaWRlbnRpZmllcg=="} {
|
||
|
tests = append(tests, struct {
|
||
|
name string
|
||
|
existing map[metricParts]struct{}
|
||
|
nmm *NodeMetricMapping
|
||
|
err error
|
||
|
}{
|
||
|
name: "identifier type " + idT + " allowed",
|
||
|
existing: map[metricParts]struct{}{},
|
||
|
nmm: func() *NodeMetricMapping {
|
||
|
nmm, err := NewNodeMetricMapping("testmetric", NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: idT,
|
||
|
Identifier: idV,
|
||
|
}, map[string]string{})
|
||
|
require.NoError(t, err)
|
||
|
return nmm
|
||
|
}(),
|
||
|
err: nil,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
err := validateNodeToAdd(tt.existing, tt.nmm)
|
||
|
require.Equal(t, tt.err, err)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestInitNodeMetricMapping(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
testname string
|
||
|
config InputClientConfig
|
||
|
expected []NodeMetricMapping
|
||
|
err error
|
||
|
}{
|
||
|
{
|
||
|
testname: "only root node",
|
||
|
config: InputClientConfig{
|
||
|
MetricName: "testmetric",
|
||
|
Timestamp: TimestampSourceTelegraf,
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
expected: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}},
|
||
|
},
|
||
|
idStr: "ns=2;s=id1",
|
||
|
metricName: "testmetric",
|
||
|
MetricTags: map[string]string{"t1": "v1"},
|
||
|
},
|
||
|
},
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
testname: "root node and group node",
|
||
|
config: InputClientConfig{
|
||
|
MetricName: "testmetric",
|
||
|
Timestamp: TimestampSourceTelegraf,
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}},
|
||
|
},
|
||
|
},
|
||
|
Groups: []NodeGroupSettings{
|
||
|
{
|
||
|
MetricName: "groupmetric",
|
||
|
Namespace: "3",
|
||
|
IdentifierType: "s",
|
||
|
Nodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "f",
|
||
|
Identifier: "id2",
|
||
|
TagsSlice: [][]string{{"t2", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
expected: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}},
|
||
|
},
|
||
|
idStr: "ns=2;s=id1",
|
||
|
metricName: "testmetric",
|
||
|
MetricTags: map[string]string{"t1": "v1"},
|
||
|
},
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "3",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id2",
|
||
|
TagsSlice: [][]string{{"t2", "v2"}},
|
||
|
},
|
||
|
idStr: "ns=3;s=id2",
|
||
|
metricName: "groupmetric",
|
||
|
MetricTags: map[string]string{"t2": "v2"},
|
||
|
},
|
||
|
},
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
testname: "only group node",
|
||
|
config: InputClientConfig{
|
||
|
MetricName: "testmetric",
|
||
|
Timestamp: TimestampSourceTelegraf,
|
||
|
Groups: []NodeGroupSettings{
|
||
|
{
|
||
|
MetricName: "groupmetric",
|
||
|
Namespace: "3",
|
||
|
IdentifierType: "s",
|
||
|
Nodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "f",
|
||
|
Identifier: "id2",
|
||
|
TagsSlice: [][]string{{"t2", "v2"}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
expected: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "3",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id2",
|
||
|
TagsSlice: [][]string{{"t2", "v2"}},
|
||
|
},
|
||
|
idStr: "ns=3;s=id2",
|
||
|
metricName: "groupmetric",
|
||
|
MetricTags: map[string]string{"t2": "v2"},
|
||
|
},
|
||
|
},
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
testname: "tags and default only default tags used",
|
||
|
config: InputClientConfig{
|
||
|
MetricName: "testmetric",
|
||
|
Timestamp: TimestampSourceTelegraf,
|
||
|
Groups: []NodeGroupSettings{
|
||
|
{
|
||
|
MetricName: "groupmetric",
|
||
|
Namespace: "3",
|
||
|
IdentifierType: "s",
|
||
|
Nodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "f",
|
||
|
Identifier: "id2",
|
||
|
TagsSlice: [][]string{{"t2", "v2"}},
|
||
|
DefaultTags: map[string]string{"t3": "v3"},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
expected: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "3",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id2",
|
||
|
TagsSlice: [][]string{{"t2", "v2"}},
|
||
|
DefaultTags: map[string]string{"t3": "v3"},
|
||
|
},
|
||
|
idStr: "ns=3;s=id2",
|
||
|
metricName: "groupmetric",
|
||
|
MetricTags: map[string]string{"t3": "v3"},
|
||
|
},
|
||
|
},
|
||
|
err: nil,
|
||
|
},
|
||
|
{
|
||
|
testname: "only root node default overrides slice",
|
||
|
config: InputClientConfig{
|
||
|
MetricName: "testmetric",
|
||
|
Timestamp: TimestampSourceTelegraf,
|
||
|
RootNodes: []NodeSettings{
|
||
|
{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}},
|
||
|
DefaultTags: map[string]string{"t3": "v3"},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
expected: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
Namespace: "2",
|
||
|
IdentifierType: "s",
|
||
|
Identifier: "id1",
|
||
|
TagsSlice: [][]string{{"t1", "v1"}},
|
||
|
DefaultTags: map[string]string{"t3": "v3"},
|
||
|
},
|
||
|
idStr: "ns=2;s=id1",
|
||
|
metricName: "testmetric",
|
||
|
MetricTags: map[string]string{"t3": "v3"},
|
||
|
},
|
||
|
},
|
||
|
err: nil,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.testname, func(t *testing.T) {
|
||
|
o := OpcUAInputClient{Config: tt.config}
|
||
|
err := o.InitNodeMetricMapping()
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, tt.expected, o.NodeMetricMapping)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestUpdateNodeValue(t *testing.T) {
|
||
|
type testStep struct {
|
||
|
nodeIdx int
|
||
|
value interface{}
|
||
|
status ua.StatusCode
|
||
|
expected interface{}
|
||
|
}
|
||
|
tests := []struct {
|
||
|
testname string
|
||
|
steps []testStep
|
||
|
}{
|
||
|
{
|
||
|
"value should update when code ok",
|
||
|
[]testStep{
|
||
|
{
|
||
|
0,
|
||
|
"Harmony",
|
||
|
ua.StatusOK,
|
||
|
"Harmony",
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
"value should not update when code bad",
|
||
|
[]testStep{
|
||
|
{
|
||
|
0,
|
||
|
"Harmony",
|
||
|
ua.StatusOK,
|
||
|
"Harmony",
|
||
|
},
|
||
|
{
|
||
|
0,
|
||
|
"Odium",
|
||
|
ua.StatusBad,
|
||
|
"Harmony",
|
||
|
},
|
||
|
{
|
||
|
0,
|
||
|
"Ati",
|
||
|
ua.StatusOK,
|
||
|
"Ati",
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
conf := &opcua.OpcUAClientConfig{
|
||
|
Endpoint: "opc.tcp://localhost:4930",
|
||
|
SecurityPolicy: "None",
|
||
|
SecurityMode: "None",
|
||
|
AuthMethod: "",
|
||
|
ConnectTimeout: config.Duration(2 * time.Second),
|
||
|
RequestTimeout: config.Duration(2 * time.Second),
|
||
|
Workarounds: opcua.OpcUAWorkarounds{},
|
||
|
}
|
||
|
c, err := conf.CreateClient(testutil.Logger{})
|
||
|
require.NoError(t, err)
|
||
|
o := OpcUAInputClient{
|
||
|
OpcUAClient: c,
|
||
|
Log: testutil.Logger{},
|
||
|
NodeMetricMapping: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "f",
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "f2",
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
LastReceivedData: make([]NodeValue, 2),
|
||
|
}
|
||
|
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.testname, func(t *testing.T) {
|
||
|
o.LastReceivedData = make([]NodeValue, 2)
|
||
|
for i, step := range tt.steps {
|
||
|
v, err := ua.NewVariant(step.value)
|
||
|
require.NoError(t, err)
|
||
|
o.UpdateNodeValue(0, &ua.DataValue{
|
||
|
Value: v,
|
||
|
Status: step.status,
|
||
|
SourceTimestamp: time.Date(2022, 03, 17, 8, 33, 00, 00, &time.Location{}).Add(time.Duration(i) * time.Second),
|
||
|
SourcePicoseconds: 0,
|
||
|
ServerTimestamp: time.Date(2022, 03, 17, 8, 33, 00, 500, &time.Location{}).Add(time.Duration(i) * time.Second),
|
||
|
ServerPicoseconds: 0,
|
||
|
})
|
||
|
require.Equal(t, step.expected, o.LastReceivedData[0].Value)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMetricForNode(t *testing.T) {
|
||
|
conf := &opcua.OpcUAClientConfig{
|
||
|
Endpoint: "opc.tcp://localhost:4930",
|
||
|
SecurityPolicy: "None",
|
||
|
SecurityMode: "None",
|
||
|
AuthMethod: "",
|
||
|
ConnectTimeout: config.Duration(2 * time.Second),
|
||
|
RequestTimeout: config.Duration(2 * time.Second),
|
||
|
Workarounds: opcua.OpcUAWorkarounds{},
|
||
|
}
|
||
|
c, err := conf.CreateClient(testutil.Logger{})
|
||
|
require.NoError(t, err)
|
||
|
o := OpcUAInputClient{
|
||
|
Config: InputClientConfig{
|
||
|
Timestamp: TimestampSourceSource,
|
||
|
},
|
||
|
OpcUAClient: c,
|
||
|
Log: testutil.Logger{},
|
||
|
LastReceivedData: make([]NodeValue, 2),
|
||
|
}
|
||
|
|
||
|
tests := []struct {
|
||
|
testname string
|
||
|
nmm []NodeMetricMapping
|
||
|
v interface{}
|
||
|
isArray bool
|
||
|
dataType ua.TypeID
|
||
|
time time.Time
|
||
|
status ua.StatusCode
|
||
|
expected telegraf.Metric
|
||
|
}{
|
||
|
{
|
||
|
testname: "metric build correctly",
|
||
|
nmm: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "fn",
|
||
|
},
|
||
|
idStr: "ns=3;s=hi",
|
||
|
metricName: "testingmetric",
|
||
|
MetricTags: map[string]string{"t1": "v1"},
|
||
|
},
|
||
|
},
|
||
|
v: 16,
|
||
|
isArray: false,
|
||
|
dataType: ua.TypeIDInt32,
|
||
|
time: time.Date(2022, 03, 17, 8, 55, 00, 00, &time.Location{}),
|
||
|
status: ua.StatusOK,
|
||
|
expected: metric.New("testingmetric",
|
||
|
map[string]string{"t1": "v1", "id": "ns=3;s=hi"},
|
||
|
map[string]interface{}{"Quality": "The operation succeeded. StatusGood (0x0)", "fn": 16},
|
||
|
time.Date(2022, 03, 17, 8, 55, 00, 00, &time.Location{})),
|
||
|
},
|
||
|
{
|
||
|
testname: "array-like metric build correctly",
|
||
|
nmm: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "fn",
|
||
|
},
|
||
|
idStr: "ns=3;s=hi",
|
||
|
metricName: "testingmetric",
|
||
|
MetricTags: map[string]string{"t1": "v1"},
|
||
|
},
|
||
|
},
|
||
|
v: []int32{16, 17},
|
||
|
isArray: true,
|
||
|
dataType: ua.TypeIDInt32,
|
||
|
time: time.Date(2022, 03, 17, 8, 55, 00, 00, &time.Location{}),
|
||
|
status: ua.StatusOK,
|
||
|
expected: metric.New("testingmetric",
|
||
|
map[string]string{"t1": "v1", "id": "ns=3;s=hi"},
|
||
|
map[string]interface{}{"Quality": "The operation succeeded. StatusGood (0x0)", "fn[0]": 16, "fn[1]": 17},
|
||
|
time.Date(2022, 03, 17, 8, 55, 00, 00, &time.Location{})),
|
||
|
},
|
||
|
{
|
||
|
testname: "nil does not panic",
|
||
|
nmm: []NodeMetricMapping{
|
||
|
{
|
||
|
Tag: NodeSettings{
|
||
|
FieldName: "fn",
|
||
|
},
|
||
|
idStr: "ns=3;s=hi",
|
||
|
metricName: "testingmetric",
|
||
|
MetricTags: map[string]string{"t1": "v1"},
|
||
|
},
|
||
|
},
|
||
|
v: nil,
|
||
|
isArray: false,
|
||
|
dataType: ua.TypeIDNull,
|
||
|
time: time.Date(2022, 03, 17, 8, 55, 00, 00, &time.Location{}),
|
||
|
status: ua.StatusOK,
|
||
|
expected: metric.New("testingmetric",
|
||
|
map[string]string{"t1": "v1", "id": "ns=3;s=hi"},
|
||
|
map[string]interface{}{"Quality": "The operation succeeded. StatusGood (0x0)"},
|
||
|
time.Date(2022, 03, 17, 8, 55, 00, 00, &time.Location{})),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.testname, func(t *testing.T) {
|
||
|
o.NodeMetricMapping = tt.nmm
|
||
|
o.LastReceivedData[0].SourceTime = tt.time
|
||
|
o.LastReceivedData[0].Quality = tt.status
|
||
|
o.LastReceivedData[0].Value = tt.v
|
||
|
o.LastReceivedData[0].DataType = tt.dataType
|
||
|
o.LastReceivedData[0].IsArray = tt.isArray
|
||
|
actual := o.MetricForNode(0)
|
||
|
require.Equal(t, tt.expected.Tags(), actual.Tags())
|
||
|
require.Equal(t, tt.expected.Fields(), actual.Fields())
|
||
|
require.Equal(t, tt.expected.Time(), actual.Time())
|
||
|
})
|
||
|
}
|
||
|
}
|