fix: Accept non-standard OPC UA OK status by implementing a configurable workaround (#10384)

Thanks!
This commit is contained in:
R290 2022-01-11 23:39:18 +01:00 committed by GitHub
parent 48b981bd4e
commit 515715fee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 17 deletions

View File

@ -89,6 +89,11 @@ Plugin minimum tested version: 1.16
# {name="", namespace="", identifier_type="", identifier=""},
# {name="", namespace="", identifier_type="", identifier=""},
#]
## Enable workarounds required by some devices to work correctly
# [inputs.opcua.workarounds]
## Set additional valid status codes, StatusOK (0x0) is always considered valid
# additional_valid_status_codes = ["0xC0"]
```
## Node Configuration

View File

@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"sort"
"strconv"
"strings"
"time"
@ -18,23 +19,28 @@ import (
"github.com/influxdata/telegraf/selfstat"
)
type OpcuaWorkarounds struct {
AdditionalValidStatusCodes []string `toml:"additional_valid_status_codes"`
}
// OpcUA type
type OpcUA struct {
MetricName string `toml:"name"`
Endpoint string `toml:"endpoint"`
SecurityPolicy string `toml:"security_policy"`
SecurityMode string `toml:"security_mode"`
Certificate string `toml:"certificate"`
PrivateKey string `toml:"private_key"`
Username string `toml:"username"`
Password string `toml:"password"`
Timestamp string `toml:"timestamp"`
AuthMethod string `toml:"auth_method"`
ConnectTimeout config.Duration `toml:"connect_timeout"`
RequestTimeout config.Duration `toml:"request_timeout"`
RootNodes []NodeSettings `toml:"nodes"`
Groups []GroupSettings `toml:"group"`
Log telegraf.Logger `toml:"-"`
MetricName string `toml:"name"`
Endpoint string `toml:"endpoint"`
SecurityPolicy string `toml:"security_policy"`
SecurityMode string `toml:"security_mode"`
Certificate string `toml:"certificate"`
PrivateKey string `toml:"private_key"`
Username string `toml:"username"`
Password string `toml:"password"`
Timestamp string `toml:"timestamp"`
AuthMethod string `toml:"auth_method"`
ConnectTimeout config.Duration `toml:"connect_timeout"`
RequestTimeout config.Duration `toml:"request_timeout"`
RootNodes []NodeSettings `toml:"nodes"`
Groups []GroupSettings `toml:"group"`
Workarounds OpcuaWorkarounds `toml:"workarounds"`
Log telegraf.Logger `toml:"-"`
nodes []Node
nodeData []OPCData
@ -50,6 +56,7 @@ type OpcUA struct {
client *opcua.Client
req *ua.ReadRequest
opts []opcua.Option
codes []ua.StatusCode
}
type NodeSettings struct {
@ -180,6 +187,11 @@ const sampleConfig = `
# {name="", namespace="", identifier_type="", identifier=""},
# {name="", namespace="", identifier_type="", identifier=""},
#]
## Enable workarounds required by some devices to work correctly
# [inputs.opcua.workarounds]
## Set additional valid status codes, StatusOK (0x0) is always considered valid
# additional_valid_status_codes = ["0xC0"]
`
// Description will appear directly above the plugin definition in the config file
@ -216,6 +228,11 @@ func (o *OpcUA) Init() error {
return err
}
err = o.setupWorkarounds()
if err != nil {
return err
}
tags := map[string]string{
"endpoint": o.Endpoint,
}
@ -480,6 +497,28 @@ func (o *OpcUA) setupOptions() error {
return err
}
func (o *OpcUA) setupWorkarounds() error {
if len(o.Workarounds.AdditionalValidStatusCodes) != 0 {
for _, c := range o.Workarounds.AdditionalValidStatusCodes {
val, err := strconv.ParseInt(c, 0, 32) // setting 32 bits to allow for safe conversion
if err != nil {
return err
}
o.codes = append(o.codes, ua.StatusCode(uint32(val)))
}
}
return nil
}
func (o *OpcUA) checkStatusCode(code ua.StatusCode) bool {
for _, val := range o.codes {
if val == code {
return true
}
}
return false
}
func (o *OpcUA) getData() error {
resp, err := o.client.Read(o.req)
if err != nil {
@ -489,7 +528,7 @@ func (o *OpcUA) getData() error {
o.ReadSuccess.Incr(1)
for i, d := range resp.Results {
o.nodeData[i].Quality = d.Status
if d.Status != ua.StatusOK {
if !o.checkStatusCode(d.Status) {
o.Log.Errorf("status not OK for node %v: %v", o.nodes[i].tag.FieldName, d.Status)
continue
}
@ -553,7 +592,7 @@ func (o *OpcUA) Gather(acc telegraf.Accumulator) error {
}
for i, n := range o.nodes {
if o.nodeData[i].Quality == ua.StatusOK {
if o.checkStatusCode(o.nodeData[i].Quality) {
fields := make(map[string]interface{})
tags := map[string]string{
"id": n.idStr,
@ -593,6 +632,7 @@ func init() {
Certificate: "/etc/telegraf/cert.pem",
PrivateKey: "/etc/telegraf/key.pem",
AuthMethod: "Anonymous",
codes: []ua.StatusCode{ua.StatusOK},
}
})
}

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/gopcua/opcua/ua"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/testutil"
)
@ -43,6 +44,7 @@ func TestClient1Integration(t *testing.T) {
o.RequestTimeout = config.Duration(1 * time.Second)
o.SecurityPolicy = "None"
o.SecurityMode = "None"
o.codes = []ua.StatusCode{ua.StatusOK}
o.Log = testutil.Logger{}
for _, tags := range testopctags {
o.RootNodes = append(o.RootNodes, MapOPCTag(tags))
@ -108,6 +110,9 @@ namespace = "0"
identifier_type = "i"
tags = [["tag1", "val1"], ["tag2", "val2"]]
nodes = [{name="name4", identifier="4000", tags=[["tag1", "override"]]}]
[inputs.opcua.workarounds]
additional_valid_status_codes = ["0xC0"]
`
c := config.NewConfig()
@ -132,6 +137,9 @@ nodes = [{name="name4", identifier="4000", tags=[["tag1", "override"]]}]
require.Len(t, o.nodes, 4)
require.Len(t, o.nodes[2].metricTags, 3)
require.Len(t, o.nodes[3].metricTags, 2)
require.Len(t, o.Workarounds.AdditionalValidStatusCodes, 1)
require.Equal(t, o.Workarounds.AdditionalValidStatusCodes[0], "0xC0")
}
func TestTagsSliceToMap(t *testing.T) {
@ -260,3 +268,24 @@ func TestValidateOPCTags(t *testing.T) {
})
}
}
func TestSetupWorkarounds(t *testing.T) {
var o OpcUA
o.codes = []ua.StatusCode{ua.StatusOK}
o.Workarounds.AdditionalValidStatusCodes = []string{"0xC0", "0x00AA0000"}
err := o.setupWorkarounds()
require.NoError(t, err)
require.Len(t, o.codes, 3)
require.Equal(t, o.codes[0], ua.StatusCode(0))
require.Equal(t, o.codes[1], ua.StatusCode(192))
require.Equal(t, o.codes[2], ua.StatusCode(11141120))
}
func TestCheckStatusCode(t *testing.T) {
var o OpcUA
o.codes = []ua.StatusCode{ua.StatusCode(0), ua.StatusCode(192), ua.StatusCode(11141120)}
require.Equal(t, o.checkStatusCode(ua.StatusCode(192)), true)
}