From ac17d0838c0894f52553db5a5301a1e1db4428b7 Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Fri, 24 Nov 2023 23:25:59 -0800 Subject: [PATCH 01/18] Structure for additional fields, option to include DataType --- plugins/common/opcua/client.go | 7 ++++++- plugins/common/opcua/input/input_client.go | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/common/opcua/client.go b/plugins/common/opcua/client.go index 03229c3035f05..aed858f451f5c 100644 --- a/plugins/common/opcua/client.go +++ b/plugins/common/opcua/client.go @@ -18,6 +18,10 @@ type OpcUAWorkarounds struct { AdditionalValidStatusCodes []string `toml:"additional_valid_status_codes"` } +type OpcUAAdditionalFields struct { + IncludeDataType bool `toml:"include_datatype"` +} + type ConnectionState opcua.ConnState const ( @@ -44,7 +48,8 @@ type OpcUAClientConfig struct { ConnectTimeout config.Duration `toml:"connect_timeout"` RequestTimeout config.Duration `toml:"request_timeout"` - Workarounds OpcUAWorkarounds `toml:"workarounds"` + OptionalFields OpcUAAdditionalFields `toml:"additional_fields"` + Workarounds OpcUAWorkarounds `toml:"workarounds"` } func (o *OpcUAClientConfig) Validate() error { diff --git a/plugins/common/opcua/input/input_client.go b/plugins/common/opcua/input/input_client.go index 5ee49d7bdeddb..a1ec90797b73b 100644 --- a/plugins/common/opcua/input/input_client.go +++ b/plugins/common/opcua/input/input_client.go @@ -421,6 +421,9 @@ func (o *OpcUAInputClient) MetricForNode(nodeIdx int) telegraf.Metric { fields[nmm.Tag.FieldName] = o.LastReceivedData[nodeIdx].Value fields["Quality"] = strings.TrimSpace(o.LastReceivedData[nodeIdx].Quality.Error()) + if o.Config.OptionalFields.IncludeDataType { + fields["DataType"] = strings.Replace(o.LastReceivedData[nodeIdx].DataType.String(), "TypeID", "", 1) + } if !o.StatusCodeOK(o.LastReceivedData[nodeIdx].Quality) { mp := newMP(nmm) o.Log.Debugf("status not OK for node %q(metric name %q, tags %q)", From 526007c6281c31f8c289ad07b7a4517332f3fca8 Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Sun, 26 Nov 2023 10:28:49 -0500 Subject: [PATCH 02/18] Add README --- plugins/inputs/opcua/README.md | 11 +++++++++++ plugins/inputs/opcua/opcua_test.go | 3 +++ 2 files changed, 14 insertions(+) diff --git a/plugins/inputs/opcua/README.md b/plugins/inputs/opcua/README.md index 137cdebce385e..2d68d50d99aa1 100644 --- a/plugins/inputs/opcua/README.md +++ b/plugins/inputs/opcua/README.md @@ -151,6 +151,11 @@ to use them. # identifier_type = "" # identifier = "" + ## Include additional Fields in each metric + # [inputs.opcua.additional_fields] + ## OPC-UA DataType + # include_datatype = true + ## Enable workarounds required by some devices to work correctly # [inputs.opcua.workarounds] ## Set additional valid status codes, StatusOK (0x0) is always considered valid @@ -180,6 +185,12 @@ This node configuration produces a metric like this: opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)" 1597820490000000000 ``` +With 'DataType' enabled in Additional Metrics, this node configuration produces a metric like this: + +```text +opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)",DataType="Float" 1597820490000000000 +``` + ## Group Configuration Groups can set default values for the namespace, identifier type, and diff --git a/plugins/inputs/opcua/opcua_test.go b/plugins/inputs/opcua/opcua_test.go index 631e760caebaf..c3379dc144cef 100644 --- a/plugins/inputs/opcua/opcua_test.go +++ b/plugins/inputs/opcua/opcua_test.go @@ -265,6 +265,9 @@ additional_valid_status_codes = ["0xC0"] [inputs.opcua.request_workarounds] use_unregistered_reads = true + +[inputs.opcua.additional_fields] +include_datatype = true ` c := config.NewConfig() From ad2f8d88a845b7ca45e9777c13149a4249409256 Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Sun, 26 Nov 2023 13:13:53 -0500 Subject: [PATCH 03/18] DataType can be included in Metrics for OPC-UA inputs --- plugins/inputs/opcua/README.md | 3 +- plugins/inputs/opcua/opcua_test.go | 72 +++++++++++ plugins/inputs/opcua/sample.conf | 5 + plugins/inputs/opcua_listener/README.md | 12 ++ .../opcua_listener/opcua_listener_test.go | 121 ++++++++++++++++++ plugins/inputs/opcua_listener/sample.conf | 6 + 6 files changed, 218 insertions(+), 1 deletion(-) diff --git a/plugins/inputs/opcua/README.md b/plugins/inputs/opcua/README.md index 2d68d50d99aa1..4ab3e6c073b7b 100644 --- a/plugins/inputs/opcua/README.md +++ b/plugins/inputs/opcua/README.md @@ -185,7 +185,8 @@ This node configuration produces a metric like this: opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)" 1597820490000000000 ``` -With 'DataType' enabled in Additional Metrics, this node configuration produces a metric like this: +With 'DataType' enabled in Additional Metrics, this node configuration +produces a metric like this: ```text opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)",DataType="Float" 1597820490000000000 diff --git a/plugins/inputs/opcua/opcua_test.go b/plugins/inputs/opcua/opcua_test.go index c3379dc144cef..2f7136d6fa33d 100644 --- a/plugins/inputs/opcua/opcua_test.go +++ b/plugins/inputs/opcua/opcua_test.go @@ -2,6 +2,7 @@ package opcua import ( "fmt" + "strings" "testing" "time" @@ -150,6 +151,76 @@ func TestReadClientIntegration(t *testing.T) { } } +func TestReadClientIntegrationAdditionalFields(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + container := testutil.Container{ + Image: "open62541/open62541", + ExposedPorts: []string{servicePort}, + WaitingFor: wait.ForAll( + wait.ForListeningPort(nat.Port(servicePort)), + wait.ForLog("TCP network layer listening on opc.tcp://"), + ), + } + err := container.Start() + require.NoError(t, err, "failed to start container") + defer container.Terminate() + + testopctags := []OPCTags{ + {"ProductName", "0", "i", "2261", "open62541 OPC UA Server"}, + {"ProductUri", "0", "i", "2262", "http://open62541.org"}, + {"ManufacturerName", "0", "i", "2263", "open62541"}, + {"badnode", "1", "i", "1337", nil}, + {"goodnode", "1", "s", "the.answer", int32(42)}, + {"DateTime", "1", "i", "51037", "0001-01-01T00:00:00Z"}, + } + + readConfig := ReadClientConfig{ + InputClientConfig: input.InputClientConfig{ + OpcUAClientConfig: opcua.OpcUAClientConfig{ + Endpoint: fmt.Sprintf("opc.tcp://%s:%s", container.Address, container.Ports[servicePort]), + SecurityPolicy: "None", + SecurityMode: "None", + AuthMethod: "Anonymous", + ConnectTimeout: config.Duration(10 * time.Second), + RequestTimeout: config.Duration(1 * time.Second), + Workarounds: opcua.OpcUAWorkarounds{}, + OptionalFields: opcua.OpcUAAdditionalFields{IncludeDataType: true}, + }, + MetricName: "testing", + RootNodes: make([]input.NodeSettings, 0), + Groups: make([]input.NodeGroupSettings, 0), + }, + } + + for _, tags := range testopctags { + readConfig.RootNodes = append(readConfig.RootNodes, MapOPCTag(tags)) + } + + client, err := readConfig.CreateReadClient(testutil.Logger{}) + require.NoError(t, err) + + err = client.Connect() + require.NoError(t, err, "Connect") + + for i, v := range client.LastReceivedData { + actualDataType := "???" + for _, k := range client.MetricForNode(i).FieldList() { + if k.Key == "DataType" { + actualDataType = fmt.Sprintf("%v", k.Value) + } + } + require.NotEqual(t, "???", actualDataType) + + // Convert returned OPC-UA DataType to String and select just the DataType + expectedDataType := strings.Replace(v.DataType.String(), "TypeID", "", 1) + + require.Equal(t, expectedDataType, actualDataType) + } +} + func TestReadClientIntegrationWithPasswordAuth(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") @@ -337,6 +408,7 @@ include_datatype = true }, o.ReadClientConfig.Groups) require.Equal(t, opcua.OpcUAWorkarounds{AdditionalValidStatusCodes: []string{"0xC0"}}, o.ReadClientConfig.Workarounds) require.Equal(t, ReadClientWorkarounds{UseUnregisteredReads: true}, o.ReadClientConfig.ReadClientWorkarounds) + require.Equal(t, opcua.OpcUAAdditionalFields{IncludeDataType: true}, o.ReadClientConfig.OptionalFields) err = o.Init() require.NoError(t, err) require.Len(t, o.client.NodeMetricMapping, 5, "incorrect number of nodes") diff --git a/plugins/inputs/opcua/sample.conf b/plugins/inputs/opcua/sample.conf index 3822bb66deaff..518a7d37da203 100644 --- a/plugins/inputs/opcua/sample.conf +++ b/plugins/inputs/opcua/sample.conf @@ -123,6 +123,11 @@ # identifier_type = "" # identifier = "" + ## Include additional Fields in each metric + # [inputs.opcua.additional_fields] + ## OPC-UA DataType + # include_datatype = true + ## Enable workarounds required by some devices to work correctly # [inputs.opcua.workarounds] ## Set additional valid status codes, StatusOK (0x0) is always considered valid diff --git a/plugins/inputs/opcua_listener/README.md b/plugins/inputs/opcua_listener/README.md index 54be90dafceae..04c75d0610747 100644 --- a/plugins/inputs/opcua_listener/README.md +++ b/plugins/inputs/opcua_listener/README.md @@ -226,6 +226,12 @@ to use them. # deadband_type = "Absolute" # deadband_value = 0.0 # + + ## Include additional Fields in each metric + # [inputs.opcua.additional_fields] + ## OPC-UA DataType + # include_datatype = true + ## Enable workarounds required by some devices to work correctly # [inputs.opcua_listener.workarounds] ## Set additional valid status codes, StatusOK (0x0) is always considered valid @@ -255,6 +261,12 @@ This node configuration produces a metric like this: opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)" 1597820490000000000 ``` +With 'DataType' enabled in Additional Metrics, this node configuration produces a metric like this: + +```text +opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)",DataType="Float" 1597820490000000000 +``` + ## Group Configuration Groups can set default values for the namespace, identifier type, tags diff --git a/plugins/inputs/opcua_listener/opcua_listener_test.go b/plugins/inputs/opcua_listener/opcua_listener_test.go index 06807f5e81400..15eebc1dc94c9 100644 --- a/plugins/inputs/opcua_listener/opcua_listener_test.go +++ b/plugins/inputs/opcua_listener/opcua_listener_test.go @@ -148,6 +148,123 @@ func TestSubscribeClientIntegration(t *testing.T) { } } +func TestSubscribeClientIntegrationAdditionalFields(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + container := testutil.Container{ + Image: "open62541/open62541", + ExposedPorts: []string{servicePort}, + WaitingFor: wait.ForAll( + wait.ForListeningPort(nat.Port(servicePort)), + wait.ForLog("TCP network layer listening on opc.tcp://"), + ), + } + err := container.Start() + require.NoError(t, err, "failed to start container") + defer container.Terminate() + + testopctags := []OPCTags{ + {"ProductName", "0", "i", "2261", "String"}, + {"ProductUri", "0", "i", "2262", "String"}, + {"ManufacturerName", "0", "i", "2263", "String"}, + {"badnode", "1", "i", "1337", "None"}, + {"goodnode", "1", "s", "the.answer", "Int32"}, + {"DateTime", "1", "i", "51037", "DateTime"}, + } + tagsRemaining := make([]string, 0, len(testopctags)) + for i, tag := range testopctags { + if tag.Want != nil { + tagsRemaining = append(tagsRemaining, testopctags[i].Name) + } + } + + subscribeConfig := SubscribeClientConfig{ + InputClientConfig: input.InputClientConfig{ + OpcUAClientConfig: opcua.OpcUAClientConfig{ + Endpoint: fmt.Sprintf("opc.tcp://%s:%s", container.Address, container.Ports[servicePort]), + SecurityPolicy: "None", + SecurityMode: "None", + AuthMethod: "Anonymous", + ConnectTimeout: config.Duration(10 * time.Second), + RequestTimeout: config.Duration(1 * time.Second), + Workarounds: opcua.OpcUAWorkarounds{}, + OptionalFields: opcua.OpcUAAdditionalFields{IncludeDataType: true}, + }, + MetricName: "testing", + RootNodes: make([]input.NodeSettings, 0), + Groups: make([]input.NodeGroupSettings, 0), + }, + SubscriptionInterval: 0, + } + for _, tags := range testopctags { + subscribeConfig.RootNodes = append(subscribeConfig.RootNodes, MapOPCTag(tags)) + } + o, err := subscribeConfig.CreateSubscribeClient(testutil.Logger{}) + require.NoError(t, err) + + // give initial setup a couple extra attempts, as on CircleCI this can be + // attempted to soon + require.Eventually(t, func() bool { + return o.SetupOptions() == nil + }, 5*time.Second, 10*time.Millisecond) + + err = o.Connect() + require.NoError(t, err, "Connection failed") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + res, err := o.StartStreamValues(ctx) + require.NoError(t, err) + + for { + select { + case m := <-res: + resTagName := "???" + resDataType := "???" + resDataTypeExpected := "???" + for fieldName, fieldValue := range m.Fields() { + switch fieldName { + case "DataType": + resDataType = fmt.Sprintf("%v", fieldValue) + default: + for _, tag := range testopctags { + if fieldName == tag.Name { + resTagName = fieldName + resDataTypeExpected = fmt.Sprintf("%v", tag.Want) + } + } + } + } + require.Equal(t, resDataTypeExpected, resDataType) + + newRemaining := make([]string, 0, len(tagsRemaining)) + for _, remainingTag := range tagsRemaining { + if resTagName != remainingTag { + newRemaining = append(newRemaining, remainingTag) + break + } + } + + if len(newRemaining) <= 0 { + return + } + + tagsRemaining = newRemaining + + case <-ctx.Done(): + msg := "" + for _, tag := range tagsRemaining { + msg += tag + ", " + } + + t.Errorf("Tags %s are remaining without a received value", msg) + return + } + } +} + func TestSubscribeClientConfig(t *testing.T) { toml := ` [[inputs.opcua_listener]] @@ -185,6 +302,9 @@ nodes = [{name="name4", identifier="4000", tags=[["tag1", "override"]]}] [inputs.opcua_listener.workarounds] additional_valid_status_codes = ["0xC0"] + +[inputs.opcua_listener.additional_fields] +include_datatype = true ` c := config.NewConfig() @@ -247,6 +367,7 @@ additional_valid_status_codes = ["0xC0"] }, }, o.SubscribeClientConfig.Groups) require.Equal(t, opcua.OpcUAWorkarounds{AdditionalValidStatusCodes: []string{"0xC0"}}, o.SubscribeClientConfig.Workarounds) + require.Equal(t, opcua.OpcUAAdditionalFields{IncludeDataType: true}, o.SubscribeClientConfig.OptionalFields) } func TestSubscribeClientConfigWithMonitoringParams(t *testing.T) { diff --git a/plugins/inputs/opcua_listener/sample.conf b/plugins/inputs/opcua_listener/sample.conf index 788502937bddc..c2ec90eb183fb 100644 --- a/plugins/inputs/opcua_listener/sample.conf +++ b/plugins/inputs/opcua_listener/sample.conf @@ -187,6 +187,12 @@ # deadband_type = "Absolute" # deadband_value = 0.0 # + + ## Include additional Fields in each metric + # [inputs.opcua.additional_fields] + ## OPC-UA DataType + # include_datatype = true + ## Enable workarounds required by some devices to work correctly # [inputs.opcua_listener.workarounds] ## Set additional valid status codes, StatusOK (0x0) is always considered valid From b5f217bff62d2deff917270a5ccf72b90eaca0eb Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Sun, 26 Nov 2023 13:21:34 -0500 Subject: [PATCH 04/18] Shortened README line --- plugins/inputs/opcua/README.md | 2 +- plugins/inputs/opcua_listener/README.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/inputs/opcua/README.md b/plugins/inputs/opcua/README.md index 4ab3e6c073b7b..756d19f4ebac2 100644 --- a/plugins/inputs/opcua/README.md +++ b/plugins/inputs/opcua/README.md @@ -185,7 +185,7 @@ This node configuration produces a metric like this: opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)" 1597820490000000000 ``` -With 'DataType' enabled in Additional Metrics, this node configuration +With 'DataType' enabled in Additional Metrics, this node configuration produces a metric like this: ```text diff --git a/plugins/inputs/opcua_listener/README.md b/plugins/inputs/opcua_listener/README.md index 04c75d0610747..4a2a8fbef7ee1 100644 --- a/plugins/inputs/opcua_listener/README.md +++ b/plugins/inputs/opcua_listener/README.md @@ -261,7 +261,8 @@ This node configuration produces a metric like this: opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)" 1597820490000000000 ``` -With 'DataType' enabled in Additional Metrics, this node configuration produces a metric like this: +With 'DataType' enabled in Additional Metrics, this node configuration +produces a metric like this: ```text opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)",DataType="Float" 1597820490000000000 From 6e410631282329e3c28f37d11290f8ebb25ca8fc Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Sun, 3 Dec 2023 21:41:49 -0500 Subject: [PATCH 05/18] Tests to use RequireMetricsEqual, fix document defaults --- .developer/telegraf-listener.conf | 150 ++++++++++++++++++ .developer/telegraf.conf | 150 ++++++++++++++++++ plugins/inputs/opcua/README.md | 6 +- plugins/inputs/opcua/opcua_test.go | 48 ++++-- plugins/inputs/opcua/sample.conf | 2 +- plugins/inputs/opcua_listener/README.md | 6 +- .../opcua_listener/opcua_listener_test.go | 89 +++++++---- plugins/inputs/opcua_listener/sample.conf | 2 +- 8 files changed, 401 insertions(+), 52 deletions(-) create mode 100644 .developer/telegraf-listener.conf create mode 100644 .developer/telegraf.conf diff --git a/.developer/telegraf-listener.conf b/.developer/telegraf-listener.conf new file mode 100644 index 0000000000000..70ced9303880e --- /dev/null +++ b/.developer/telegraf-listener.conf @@ -0,0 +1,150 @@ +[agent] +debug = true + +# Retrieve data from OPCUA devices +[[inputs.opcua_listener]] + ## Metric name + name = "opcua" + # + ## OPC UA Endpoint URL + endpoint = "opc.tcp://localhost:48400" + # + ## Maximum time allowed to establish a connect to the endpoint. + connect_timeout = "10s" + # + ## Maximum time allowed for a request over the established connection. + request_timeout = "5s" + # + ## Security policy, one of "None", "Basic128Rsa15", "Basic256", + ## "Basic256Sha256", or "auto" + security_policy = "None" + # + ## Security mode, one of "None", "Sign", "SignAndEncrypt", or "auto" + security_mode = "None" + # + ## Path to cert.pem. Required when security mode or policy isn't "None". + ## If cert path is not supplied, self-signed cert and key will be generated. + # certificate = "/etc/telegraf/cert.pem" + # + ## Path to private key.pem. Required when security mode or policy isn't "None". + ## If key path is not supplied, self-signed cert and key will be generated. + # private_key = "/etc/telegraf/key.pem" + # + ## Authentication Method, one of "Certificate", "UserName", or "Anonymous". To + ## authenticate using a specific ID, select 'Certificate' or 'UserName' + auth_method = "Anonymous" + # + ## Username. Required for auth_method = "UserName" + # username = "" + # + ## Password. Required for auth_method = "UserName" + # password = "" + # + ## Option to select the metric timestamp to use. Valid options are: + ## "gather" -- uses the time of receiving the data in telegraf + ## "server" -- uses the timestamp provided by the server + ## "source" -- uses the timestamp provided by the source + timestamp = "gather" + # + ## Node ID configuration + ## name - field name to use in the output + ## namespace - OPC UA namespace of the node (integer value 0 thru 3) + ## identifier_type - OPC UA ID type (s=string, i=numeric, g=guid, b=opaque) + ## identifier - OPC UA ID (tag as shown in opcua browser) + ## tags - extra tags to be added to the output metric (optional); deprecated in 1.25.0; use default_tags + ## default_tags - extra tags to be added to the output metric (optional) + ## + ## Use either the inline notation or the bracketed notation, not both. + # + ## Inline notation (default_tags not supported yet) + # nodes = [ + # {name="", namespace="", identifier_type="", identifier="", tags=[["tag1", "value1"], ["tag2", "value2"]}, + # {name="", namespace="", identifier_type="", identifier=""}, + # ] + # + ## Bracketed notation + # [[inputs.opcua_listener.nodes]] + # name = "node1" + # namespace = "" + # identifier_type = "" + # identifier = "" + # default_tags = { tag1 = "value1", tag2 = "value2" } + # + # [[inputs.opcua_listener.nodes]] + # name = "node2" + # namespace = "" + # identifier_type = "" + # identifier = "" + # + ## Node Group + ## Sets defaults so they aren't required in every node. + ## Default values can be set for: + ## * Metric name + ## * OPC UA namespace + ## * Identifier + ## * Default tags + ## + ## Multiple node groups are allowed + #[[inputs.opcua_listener.group]] + ## Group Metric name. Overrides the top level name. If unset, the + ## top level name is used. + # name = + # + ## Group default namespace. If a node in the group doesn't set its + ## namespace, this is used. + # namespace = + # + ## Group default identifier type. If a node in the group doesn't set its + ## namespace, this is used. + # identifier_type = + # + ## Default tags that are applied to every node in this group. Can be + ## overwritten in a node by setting a different value for the tag name. + ## example: default_tags = { tag1 = "value1" } + # default_tags = {} + # + ## Node ID Configuration. Array of nodes with the same settings as above. + ## Use either the inline notation or the bracketed notation, not both. + # + # Inline notation (default_tags not supported yet) + nodes = [ + {name="tag1", namespace="1", identifier_type="s", identifier="the.answer"}, + {name="tag2", namespace="1", identifier_type="i", identifier="43701"}, + {name="tag3", namespace="1", identifier_type="i", identifier="2345"}, + ] + + ## Bracketed notation + # [[inputs.opcua_listener.group.nodes]] + # name = "node1" + # namespace = "" + # identifier_type = "" + # identifier = "" + # default_tags = { tag1 = "override1", tag2 = "value2" } + # + # [[inputs.opcua_listener.group.nodes]] + # # name = "node2" + # namespace = "3" + # identifier_type = "s" + # identifier = "FastUInt1" + + ## Enable workarounds required by some devices to work correctly + # [inputs.opcua_listener.workarounds] + ## Set additional valid status codes, StatusOK (0x0) is always considered valid + # additional_valid_status_codes = ["0xC0"] + + # [inputs.opcua_listener.request_workarounds] + ## Use unregistered reads instead of registered reads + # use_unregistered_reads = false + + [inputs.opcua_listener.additional_fields] + include_datatype = true + +[[outputs.file]] + ## Files to write to, "stdout" is a specially handled file. + files = ["stdout"] + + ## Data format to output. + ## Each data format has its own unique set of configuration options, read + ## more about them here: + ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md + data_format = "influx" diff --git a/.developer/telegraf.conf b/.developer/telegraf.conf new file mode 100644 index 0000000000000..33deac0013d56 --- /dev/null +++ b/.developer/telegraf.conf @@ -0,0 +1,150 @@ +[agent] +debug = true + +# Retrieve data from OPCUA devices +[[inputs.opcua]] + ## Metric name + name = "opcua" + # + ## OPC UA Endpoint URL + endpoint = "opc.tcp://localhost:48400" + # + ## Maximum time allowed to establish a connect to the endpoint. + connect_timeout = "10s" + # + ## Maximum time allowed for a request over the established connection. + request_timeout = "5s" + # + ## Security policy, one of "None", "Basic128Rsa15", "Basic256", + ## "Basic256Sha256", or "auto" + security_policy = "None" + # + ## Security mode, one of "None", "Sign", "SignAndEncrypt", or "auto" + security_mode = "None" + # + ## Path to cert.pem. Required when security mode or policy isn't "None". + ## If cert path is not supplied, self-signed cert and key will be generated. + # certificate = "/etc/telegraf/cert.pem" + # + ## Path to private key.pem. Required when security mode or policy isn't "None". + ## If key path is not supplied, self-signed cert and key will be generated. + # private_key = "/etc/telegraf/key.pem" + # + ## Authentication Method, one of "Certificate", "UserName", or "Anonymous". To + ## authenticate using a specific ID, select 'Certificate' or 'UserName' + auth_method = "Anonymous" + # + ## Username. Required for auth_method = "UserName" + # username = "" + # + ## Password. Required for auth_method = "UserName" + # password = "" + # + ## Option to select the metric timestamp to use. Valid options are: + ## "gather" -- uses the time of receiving the data in telegraf + ## "server" -- uses the timestamp provided by the server + ## "source" -- uses the timestamp provided by the source + timestamp = "gather" + # + ## Node ID configuration + ## name - field name to use in the output + ## namespace - OPC UA namespace of the node (integer value 0 thru 3) + ## identifier_type - OPC UA ID type (s=string, i=numeric, g=guid, b=opaque) + ## identifier - OPC UA ID (tag as shown in opcua browser) + ## tags - extra tags to be added to the output metric (optional); deprecated in 1.25.0; use default_tags + ## default_tags - extra tags to be added to the output metric (optional) + ## + ## Use either the inline notation or the bracketed notation, not both. + # + ## Inline notation (default_tags not supported yet) + # nodes = [ + # {name="", namespace="", identifier_type="", identifier="", tags=[["tag1", "value1"], ["tag2", "value2"]}, + # {name="", namespace="", identifier_type="", identifier=""}, + # ] + # + ## Bracketed notation + # [[inputs.opcua.nodes]] + # name = "node1" + # namespace = "" + # identifier_type = "" + # identifier = "" + # default_tags = { tag1 = "value1", tag2 = "value2" } + # + # [[inputs.opcua.nodes]] + # name = "node2" + # namespace = "" + # identifier_type = "" + # identifier = "" + # + ## Node Group + ## Sets defaults so they aren't required in every node. + ## Default values can be set for: + ## * Metric name + ## * OPC UA namespace + ## * Identifier + ## * Default tags + ## + ## Multiple node groups are allowed + #[[inputs.opcua.group]] + ## Group Metric name. Overrides the top level name. If unset, the + ## top level name is used. + # name = + # + ## Group default namespace. If a node in the group doesn't set its + ## namespace, this is used. + # namespace = + # + ## Group default identifier type. If a node in the group doesn't set its + ## namespace, this is used. + # identifier_type = + # + ## Default tags that are applied to every node in this group. Can be + ## overwritten in a node by setting a different value for the tag name. + ## example: default_tags = { tag1 = "value1" } + # default_tags = {} + # + ## Node ID Configuration. Array of nodes with the same settings as above. + ## Use either the inline notation or the bracketed notation, not both. + # + # Inline notation (default_tags not supported yet) + nodes = [ + {name="tag1", namespace="1", identifier_type="s", identifier="the.answer"}, + {name="tag2", namespace="1", identifier_type="i", identifier="43701"}, + {name="tag3", namespace="1", identifier_type="i", identifier="2345"}, + ] + + ## Bracketed notation + # [[inputs.opcua.group.nodes]] + # name = "node1" + # namespace = "" + # identifier_type = "" + # identifier = "" + # default_tags = { tag1 = "override1", tag2 = "value2" } + # + # [[inputs.opcua.group.nodes]] + # # name = "node2" + # namespace = "3" + # identifier_type = "s" + # identifier = "FastUInt1" + + ## 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"] + + # [inputs.opcua.request_workarounds] + ## Use unregistered reads instead of registered reads + # use_unregistered_reads = false + + [inputs.opcua.additional_fields] + include_datatype = true + +[[outputs.file]] + ## Files to write to, "stdout" is a specially handled file. + files = ["stdout"] + + ## Data format to output. + ## Each data format has its own unique set of configuration options, read + ## more about them here: + ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md + data_format = "influx" diff --git a/plugins/inputs/opcua/README.md b/plugins/inputs/opcua/README.md index 756d19f4ebac2..5d31923f345ef 100644 --- a/plugins/inputs/opcua/README.md +++ b/plugins/inputs/opcua/README.md @@ -154,7 +154,7 @@ to use them. ## Include additional Fields in each metric # [inputs.opcua.additional_fields] ## OPC-UA DataType - # include_datatype = true + # include_datatype = false ## Enable workarounds required by some devices to work correctly # [inputs.opcua.workarounds] @@ -182,14 +182,14 @@ To gather data from this node enter the following line into the 'nodes' property This node configuration produces a metric like this: ```text -opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)" 1597820490000000000 +opcua,id=ns\=3;s\=Temperature temp=79.0,Quality="OK (0x0)" 1597820490000000000 ``` With 'DataType' enabled in Additional Metrics, this node configuration produces a metric like this: ```text -opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)",DataType="Float" 1597820490000000000 +opcua,id=ns\=3;s\=Temperature temp=79.0,Quality="OK (0x0)",DataType="Float" 1597820490000000000 ``` ## Group Configuration diff --git a/plugins/inputs/opcua/opcua_test.go b/plugins/inputs/opcua/opcua_test.go index 2f7136d6fa33d..26fb91c9454cd 100644 --- a/plugins/inputs/opcua/opcua_test.go +++ b/plugins/inputs/opcua/opcua_test.go @@ -2,7 +2,6 @@ package opcua import ( "fmt" - "strings" "testing" "time" @@ -10,7 +9,9 @@ import ( "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/wait" + "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/plugins/common/opcua/input" "github.com/influxdata/telegraf/testutil" @@ -176,6 +177,35 @@ func TestReadClientIntegrationAdditionalFields(t *testing.T) { {"goodnode", "1", "s", "the.answer", int32(42)}, {"DateTime", "1", "i", "51037", "0001-01-01T00:00:00Z"}, } + testopctypes := []string{ + "String", + "String", + "String", + "Null", + "Int32", + "DateTime", + } + testopcquality := []string{ + "OK (0x0)", + "OK (0x0)", + "OK (0x0)", + "User does not have permission to perform the requested operation. StatusBadUserAccessDenied (0x801F0000)", + "OK (0x0)", + "OK (0x0)", + } + expectedopcmetrics := []telegraf.Metric{} + for i, x := range testopctags { + now := time.Now() + tags := map[string]string{ + "id": fmt.Sprintf("ns=%s;%s=%s", x.Namespace, x.IdentifierType, x.Identifier), + } + fields := map[string]interface{}{ + x.Name: x.Want, + "Quality": testopcquality[i], + "DataType": testopctypes[i], + } + expectedopcmetrics = append(expectedopcmetrics, metric.New("testing", tags, fields, now)) + } readConfig := ReadClientConfig{ InputClientConfig: input.InputClientConfig{ @@ -205,20 +235,12 @@ func TestReadClientIntegrationAdditionalFields(t *testing.T) { err = client.Connect() require.NoError(t, err, "Connect") - for i, v := range client.LastReceivedData { - actualDataType := "???" - for _, k := range client.MetricForNode(i).FieldList() { - if k.Key == "DataType" { - actualDataType = fmt.Sprintf("%v", k.Value) - } - } - require.NotEqual(t, "???", actualDataType) - - // Convert returned OPC-UA DataType to String and select just the DataType - expectedDataType := strings.Replace(v.DataType.String(), "TypeID", "", 1) + actualopcmetrics := []telegraf.Metric{} - require.Equal(t, expectedDataType, actualDataType) + for i := range client.LastReceivedData { + actualopcmetrics = append(actualopcmetrics, client.MetricForNode(i)) } + testutil.RequireMetricsEqual(t, expectedopcmetrics, actualopcmetrics, testutil.IgnoreTime()) } func TestReadClientIntegrationWithPasswordAuth(t *testing.T) { diff --git a/plugins/inputs/opcua/sample.conf b/plugins/inputs/opcua/sample.conf index 518a7d37da203..cd3565cc0329c 100644 --- a/plugins/inputs/opcua/sample.conf +++ b/plugins/inputs/opcua/sample.conf @@ -126,7 +126,7 @@ ## Include additional Fields in each metric # [inputs.opcua.additional_fields] ## OPC-UA DataType - # include_datatype = true + # include_datatype = false ## Enable workarounds required by some devices to work correctly # [inputs.opcua.workarounds] diff --git a/plugins/inputs/opcua_listener/README.md b/plugins/inputs/opcua_listener/README.md index 4a2a8fbef7ee1..4ab0ceb73a10b 100644 --- a/plugins/inputs/opcua_listener/README.md +++ b/plugins/inputs/opcua_listener/README.md @@ -230,7 +230,7 @@ to use them. ## Include additional Fields in each metric # [inputs.opcua.additional_fields] ## OPC-UA DataType - # include_datatype = true + # include_datatype = false ## Enable workarounds required by some devices to work correctly # [inputs.opcua_listener.workarounds] @@ -258,14 +258,14 @@ To gather data from this node enter the following line into the 'nodes' property This node configuration produces a metric like this: ```text -opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)" 1597820490000000000 +opcua,id=ns\=3;s\=Temperature temp=79.0,Quality="OK (0x0)" 1597820490000000000 ``` With 'DataType' enabled in Additional Metrics, this node configuration produces a metric like this: ```text -opcua,id=ns\=3;s\=Temperature temp=79.0,quality="OK (0x0)",DataType="Float" 1597820490000000000 +opcua,id=ns\=3;s\=Temperature temp=79.0,Quality="OK (0x0)",DataType="Float" 1597820490000000000 ``` ## Group Configuration diff --git a/plugins/inputs/opcua_listener/opcua_listener_test.go b/plugins/inputs/opcua_listener/opcua_listener_test.go index 15eebc1dc94c9..ced7023bdd0e9 100644 --- a/plugins/inputs/opcua_listener/opcua_listener_test.go +++ b/plugins/inputs/opcua_listener/opcua_listener_test.go @@ -11,7 +11,9 @@ import ( "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/wait" + "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/plugins/common/opcua/input" "github.com/influxdata/telegraf/testutil" @@ -166,13 +168,43 @@ func TestSubscribeClientIntegrationAdditionalFields(t *testing.T) { defer container.Terminate() testopctags := []OPCTags{ - {"ProductName", "0", "i", "2261", "String"}, - {"ProductUri", "0", "i", "2262", "String"}, - {"ManufacturerName", "0", "i", "2263", "String"}, - {"badnode", "1", "i", "1337", "None"}, - {"goodnode", "1", "s", "the.answer", "Int32"}, - {"DateTime", "1", "i", "51037", "DateTime"}, + {"ProductName", "0", "i", "2261", "open62541 OPC UA Server"}, + {"ProductUri", "0", "i", "2262", "http://open62541.org"}, + {"ManufacturerName", "0", "i", "2263", "open62541"}, + {"badnode", "1", "i", "1337", nil}, + {"goodnode", "1", "s", "the.answer", int32(42)}, + {"DateTime", "1", "i", "51037", "0001-01-01T00:00:00Z"}, + } + testopctypes := []string{ + "String", + "String", + "String", + "Null", + "Int32", + "DateTime", + } + testopcquality := []string{ + "OK (0x0)", + "OK (0x0)", + "OK (0x0)", + "User does not have permission to perform the requested operation. StatusBadUserAccessDenied (0x801F0000)", + "OK (0x0)", + "OK (0x0)", + } + expectedopcmetrics := []telegraf.Metric{} + for i, x := range testopctags { + now := time.Now() + tags := map[string]string{ + "id": fmt.Sprintf("ns=%s;%s=%s", x.Namespace, x.IdentifierType, x.Identifier), + } + fields := map[string]interface{}{ + x.Name: x.Want, + "Quality": testopcquality[i], + "DataType": testopctypes[i], + } + expectedopcmetrics = append(expectedopcmetrics, metric.New("testing", tags, fields, now)) } + tagsRemaining := make([]string, 0, len(testopctags)) for i, tag := range testopctags { if tag.Want != nil { @@ -221,38 +253,33 @@ func TestSubscribeClientIntegrationAdditionalFields(t *testing.T) { for { select { case m := <-res: - resTagName := "???" - resDataType := "???" - resDataTypeExpected := "???" for fieldName, fieldValue := range m.Fields() { - switch fieldName { - case "DataType": - resDataType = fmt.Sprintf("%v", fieldValue) - default: - for _, tag := range testopctags { - if fieldName == tag.Name { - resTagName = fieldName - resDataTypeExpected = fmt.Sprintf("%v", tag.Want) + for _, tag := range testopctags { + if fieldName != tag.Name { + continue + } + if tag.Want == nil { + t.Errorf("Tag: %s has value: %v", tag.Name, fieldValue) + return + } + + newRemaining := make([]string, 0, len(tagsRemaining)) + for _, remainingTag := range tagsRemaining { + if fieldName != remainingTag { + newRemaining = append(newRemaining, remainingTag) + break } } - } - } - require.Equal(t, resDataTypeExpected, resDataType) - newRemaining := make([]string, 0, len(tagsRemaining)) - for _, remainingTag := range tagsRemaining { - if resTagName != remainingTag { - newRemaining = append(newRemaining, remainingTag) - break + if len(newRemaining) <= 0 { + return + } + // Test if the received metric matches one of the expected + testutil.RequireMetricsSubset(t, []telegraf.Metric{m}, expectedopcmetrics, testutil.IgnoreTime()) + tagsRemaining = newRemaining } } - if len(newRemaining) <= 0 { - return - } - - tagsRemaining = newRemaining - case <-ctx.Done(): msg := "" for _, tag := range tagsRemaining { diff --git a/plugins/inputs/opcua_listener/sample.conf b/plugins/inputs/opcua_listener/sample.conf index c2ec90eb183fb..65ed1e73352e0 100644 --- a/plugins/inputs/opcua_listener/sample.conf +++ b/plugins/inputs/opcua_listener/sample.conf @@ -191,7 +191,7 @@ ## Include additional Fields in each metric # [inputs.opcua.additional_fields] ## OPC-UA DataType - # include_datatype = true + # include_datatype = false ## Enable workarounds required by some devices to work correctly # [inputs.opcua_listener.workarounds] From 5576919b4293e983d756d48f3e846054fe9353e7 Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Sun, 3 Dec 2023 22:14:22 -0500 Subject: [PATCH 06/18] Removed developer files --- .developer/telegraf-listener.conf | 150 ------------------------------ .developer/telegraf.conf | 150 ------------------------------ 2 files changed, 300 deletions(-) delete mode 100644 .developer/telegraf-listener.conf delete mode 100644 .developer/telegraf.conf diff --git a/.developer/telegraf-listener.conf b/.developer/telegraf-listener.conf deleted file mode 100644 index 70ced9303880e..0000000000000 --- a/.developer/telegraf-listener.conf +++ /dev/null @@ -1,150 +0,0 @@ -[agent] -debug = true - -# Retrieve data from OPCUA devices -[[inputs.opcua_listener]] - ## Metric name - name = "opcua" - # - ## OPC UA Endpoint URL - endpoint = "opc.tcp://localhost:48400" - # - ## Maximum time allowed to establish a connect to the endpoint. - connect_timeout = "10s" - # - ## Maximum time allowed for a request over the established connection. - request_timeout = "5s" - # - ## Security policy, one of "None", "Basic128Rsa15", "Basic256", - ## "Basic256Sha256", or "auto" - security_policy = "None" - # - ## Security mode, one of "None", "Sign", "SignAndEncrypt", or "auto" - security_mode = "None" - # - ## Path to cert.pem. Required when security mode or policy isn't "None". - ## If cert path is not supplied, self-signed cert and key will be generated. - # certificate = "/etc/telegraf/cert.pem" - # - ## Path to private key.pem. Required when security mode or policy isn't "None". - ## If key path is not supplied, self-signed cert and key will be generated. - # private_key = "/etc/telegraf/key.pem" - # - ## Authentication Method, one of "Certificate", "UserName", or "Anonymous". To - ## authenticate using a specific ID, select 'Certificate' or 'UserName' - auth_method = "Anonymous" - # - ## Username. Required for auth_method = "UserName" - # username = "" - # - ## Password. Required for auth_method = "UserName" - # password = "" - # - ## Option to select the metric timestamp to use. Valid options are: - ## "gather" -- uses the time of receiving the data in telegraf - ## "server" -- uses the timestamp provided by the server - ## "source" -- uses the timestamp provided by the source - timestamp = "gather" - # - ## Node ID configuration - ## name - field name to use in the output - ## namespace - OPC UA namespace of the node (integer value 0 thru 3) - ## identifier_type - OPC UA ID type (s=string, i=numeric, g=guid, b=opaque) - ## identifier - OPC UA ID (tag as shown in opcua browser) - ## tags - extra tags to be added to the output metric (optional); deprecated in 1.25.0; use default_tags - ## default_tags - extra tags to be added to the output metric (optional) - ## - ## Use either the inline notation or the bracketed notation, not both. - # - ## Inline notation (default_tags not supported yet) - # nodes = [ - # {name="", namespace="", identifier_type="", identifier="", tags=[["tag1", "value1"], ["tag2", "value2"]}, - # {name="", namespace="", identifier_type="", identifier=""}, - # ] - # - ## Bracketed notation - # [[inputs.opcua_listener.nodes]] - # name = "node1" - # namespace = "" - # identifier_type = "" - # identifier = "" - # default_tags = { tag1 = "value1", tag2 = "value2" } - # - # [[inputs.opcua_listener.nodes]] - # name = "node2" - # namespace = "" - # identifier_type = "" - # identifier = "" - # - ## Node Group - ## Sets defaults so they aren't required in every node. - ## Default values can be set for: - ## * Metric name - ## * OPC UA namespace - ## * Identifier - ## * Default tags - ## - ## Multiple node groups are allowed - #[[inputs.opcua_listener.group]] - ## Group Metric name. Overrides the top level name. If unset, the - ## top level name is used. - # name = - # - ## Group default namespace. If a node in the group doesn't set its - ## namespace, this is used. - # namespace = - # - ## Group default identifier type. If a node in the group doesn't set its - ## namespace, this is used. - # identifier_type = - # - ## Default tags that are applied to every node in this group. Can be - ## overwritten in a node by setting a different value for the tag name. - ## example: default_tags = { tag1 = "value1" } - # default_tags = {} - # - ## Node ID Configuration. Array of nodes with the same settings as above. - ## Use either the inline notation or the bracketed notation, not both. - # - # Inline notation (default_tags not supported yet) - nodes = [ - {name="tag1", namespace="1", identifier_type="s", identifier="the.answer"}, - {name="tag2", namespace="1", identifier_type="i", identifier="43701"}, - {name="tag3", namespace="1", identifier_type="i", identifier="2345"}, - ] - - ## Bracketed notation - # [[inputs.opcua_listener.group.nodes]] - # name = "node1" - # namespace = "" - # identifier_type = "" - # identifier = "" - # default_tags = { tag1 = "override1", tag2 = "value2" } - # - # [[inputs.opcua_listener.group.nodes]] - # # name = "node2" - # namespace = "3" - # identifier_type = "s" - # identifier = "FastUInt1" - - ## Enable workarounds required by some devices to work correctly - # [inputs.opcua_listener.workarounds] - ## Set additional valid status codes, StatusOK (0x0) is always considered valid - # additional_valid_status_codes = ["0xC0"] - - # [inputs.opcua_listener.request_workarounds] - ## Use unregistered reads instead of registered reads - # use_unregistered_reads = false - - [inputs.opcua_listener.additional_fields] - include_datatype = true - -[[outputs.file]] - ## Files to write to, "stdout" is a specially handled file. - files = ["stdout"] - - ## Data format to output. - ## Each data format has its own unique set of configuration options, read - ## more about them here: - ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md - data_format = "influx" diff --git a/.developer/telegraf.conf b/.developer/telegraf.conf deleted file mode 100644 index 33deac0013d56..0000000000000 --- a/.developer/telegraf.conf +++ /dev/null @@ -1,150 +0,0 @@ -[agent] -debug = true - -# Retrieve data from OPCUA devices -[[inputs.opcua]] - ## Metric name - name = "opcua" - # - ## OPC UA Endpoint URL - endpoint = "opc.tcp://localhost:48400" - # - ## Maximum time allowed to establish a connect to the endpoint. - connect_timeout = "10s" - # - ## Maximum time allowed for a request over the established connection. - request_timeout = "5s" - # - ## Security policy, one of "None", "Basic128Rsa15", "Basic256", - ## "Basic256Sha256", or "auto" - security_policy = "None" - # - ## Security mode, one of "None", "Sign", "SignAndEncrypt", or "auto" - security_mode = "None" - # - ## Path to cert.pem. Required when security mode or policy isn't "None". - ## If cert path is not supplied, self-signed cert and key will be generated. - # certificate = "/etc/telegraf/cert.pem" - # - ## Path to private key.pem. Required when security mode or policy isn't "None". - ## If key path is not supplied, self-signed cert and key will be generated. - # private_key = "/etc/telegraf/key.pem" - # - ## Authentication Method, one of "Certificate", "UserName", or "Anonymous". To - ## authenticate using a specific ID, select 'Certificate' or 'UserName' - auth_method = "Anonymous" - # - ## Username. Required for auth_method = "UserName" - # username = "" - # - ## Password. Required for auth_method = "UserName" - # password = "" - # - ## Option to select the metric timestamp to use. Valid options are: - ## "gather" -- uses the time of receiving the data in telegraf - ## "server" -- uses the timestamp provided by the server - ## "source" -- uses the timestamp provided by the source - timestamp = "gather" - # - ## Node ID configuration - ## name - field name to use in the output - ## namespace - OPC UA namespace of the node (integer value 0 thru 3) - ## identifier_type - OPC UA ID type (s=string, i=numeric, g=guid, b=opaque) - ## identifier - OPC UA ID (tag as shown in opcua browser) - ## tags - extra tags to be added to the output metric (optional); deprecated in 1.25.0; use default_tags - ## default_tags - extra tags to be added to the output metric (optional) - ## - ## Use either the inline notation or the bracketed notation, not both. - # - ## Inline notation (default_tags not supported yet) - # nodes = [ - # {name="", namespace="", identifier_type="", identifier="", tags=[["tag1", "value1"], ["tag2", "value2"]}, - # {name="", namespace="", identifier_type="", identifier=""}, - # ] - # - ## Bracketed notation - # [[inputs.opcua.nodes]] - # name = "node1" - # namespace = "" - # identifier_type = "" - # identifier = "" - # default_tags = { tag1 = "value1", tag2 = "value2" } - # - # [[inputs.opcua.nodes]] - # name = "node2" - # namespace = "" - # identifier_type = "" - # identifier = "" - # - ## Node Group - ## Sets defaults so they aren't required in every node. - ## Default values can be set for: - ## * Metric name - ## * OPC UA namespace - ## * Identifier - ## * Default tags - ## - ## Multiple node groups are allowed - #[[inputs.opcua.group]] - ## Group Metric name. Overrides the top level name. If unset, the - ## top level name is used. - # name = - # - ## Group default namespace. If a node in the group doesn't set its - ## namespace, this is used. - # namespace = - # - ## Group default identifier type. If a node in the group doesn't set its - ## namespace, this is used. - # identifier_type = - # - ## Default tags that are applied to every node in this group. Can be - ## overwritten in a node by setting a different value for the tag name. - ## example: default_tags = { tag1 = "value1" } - # default_tags = {} - # - ## Node ID Configuration. Array of nodes with the same settings as above. - ## Use either the inline notation or the bracketed notation, not both. - # - # Inline notation (default_tags not supported yet) - nodes = [ - {name="tag1", namespace="1", identifier_type="s", identifier="the.answer"}, - {name="tag2", namespace="1", identifier_type="i", identifier="43701"}, - {name="tag3", namespace="1", identifier_type="i", identifier="2345"}, - ] - - ## Bracketed notation - # [[inputs.opcua.group.nodes]] - # name = "node1" - # namespace = "" - # identifier_type = "" - # identifier = "" - # default_tags = { tag1 = "override1", tag2 = "value2" } - # - # [[inputs.opcua.group.nodes]] - # # name = "node2" - # namespace = "3" - # identifier_type = "s" - # identifier = "FastUInt1" - - ## 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"] - - # [inputs.opcua.request_workarounds] - ## Use unregistered reads instead of registered reads - # use_unregistered_reads = false - - [inputs.opcua.additional_fields] - include_datatype = true - -[[outputs.file]] - ## Files to write to, "stdout" is a specially handled file. - files = ["stdout"] - - ## Data format to output. - ## Each data format has its own unique set of configuration options, read - ## more about them here: - ## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md - data_format = "influx" From 94c354f079c104dd9e8402fe4478f44e07eefcba Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Mon, 4 Dec 2023 20:04:11 -0500 Subject: [PATCH 07/18] Change OptionalFields from sub-struct to list --- plugins/common/opcua/client.go | 8 ++------ plugins/common/opcua/input/input_client.go | 2 +- plugins/inputs/opcua/README.md | 12 ++++++------ plugins/inputs/opcua/opcua_test.go | 8 ++++---- plugins/inputs/opcua/sample.conf | 10 +++++----- plugins/inputs/opcua_listener/README.md | 12 ++++++------ plugins/inputs/opcua_listener/opcua_listener_test.go | 11 ++++++----- plugins/inputs/opcua_listener/sample.conf | 10 +++++----- 8 files changed, 35 insertions(+), 38 deletions(-) diff --git a/plugins/common/opcua/client.go b/plugins/common/opcua/client.go index aed858f451f5c..8179538557b5e 100644 --- a/plugins/common/opcua/client.go +++ b/plugins/common/opcua/client.go @@ -18,10 +18,6 @@ type OpcUAWorkarounds struct { AdditionalValidStatusCodes []string `toml:"additional_valid_status_codes"` } -type OpcUAAdditionalFields struct { - IncludeDataType bool `toml:"include_datatype"` -} - type ConnectionState opcua.ConnState const ( @@ -48,8 +44,8 @@ type OpcUAClientConfig struct { ConnectTimeout config.Duration `toml:"connect_timeout"` RequestTimeout config.Duration `toml:"request_timeout"` - OptionalFields OpcUAAdditionalFields `toml:"additional_fields"` - Workarounds OpcUAWorkarounds `toml:"workarounds"` + OptionalFields []string `toml:"optional_fields"` + Workarounds OpcUAWorkarounds `toml:"workarounds"` } func (o *OpcUAClientConfig) Validate() error { diff --git a/plugins/common/opcua/input/input_client.go b/plugins/common/opcua/input/input_client.go index a1ec90797b73b..f147710811bf6 100644 --- a/plugins/common/opcua/input/input_client.go +++ b/plugins/common/opcua/input/input_client.go @@ -421,7 +421,7 @@ func (o *OpcUAInputClient) MetricForNode(nodeIdx int) telegraf.Metric { fields[nmm.Tag.FieldName] = o.LastReceivedData[nodeIdx].Value fields["Quality"] = strings.TrimSpace(o.LastReceivedData[nodeIdx].Quality.Error()) - if o.Config.OptionalFields.IncludeDataType { + if choice.Contains("DataType", o.Config.OptionalFields) { fields["DataType"] = strings.Replace(o.LastReceivedData[nodeIdx].DataType.String(), "TypeID", "", 1) } if !o.StatusCodeOK(o.LastReceivedData[nodeIdx].Quality) { diff --git a/plugins/inputs/opcua/README.md b/plugins/inputs/opcua/README.md index 5d31923f345ef..099e6ea5c2f3b 100644 --- a/plugins/inputs/opcua/README.md +++ b/plugins/inputs/opcua/README.md @@ -71,6 +71,11 @@ to use them. ## "source" -- uses the timestamp provided by the source # timestamp = "gather" # + ## Include additional Fields in each metric + ## Available options are: + ## DataType -- OPC-UA Data Type (string) + # optional_fields = [] + # ## Node ID configuration ## name - field name to use in the output ## namespace - OPC UA namespace of the node (integer value 0 thru 3) @@ -151,11 +156,6 @@ to use them. # identifier_type = "" # identifier = "" - ## Include additional Fields in each metric - # [inputs.opcua.additional_fields] - ## OPC-UA DataType - # include_datatype = false - ## Enable workarounds required by some devices to work correctly # [inputs.opcua.workarounds] ## Set additional valid status codes, StatusOK (0x0) is always considered valid @@ -185,7 +185,7 @@ This node configuration produces a metric like this: opcua,id=ns\=3;s\=Temperature temp=79.0,Quality="OK (0x0)" 1597820490000000000 ``` -With 'DataType' enabled in Additional Metrics, this node configuration +With 'DataType' entered in Additional Metrics, this node configuration produces a metric like this: ```text diff --git a/plugins/inputs/opcua/opcua_test.go b/plugins/inputs/opcua/opcua_test.go index 26fb91c9454cd..c6806e47faff8 100644 --- a/plugins/inputs/opcua/opcua_test.go +++ b/plugins/inputs/opcua/opcua_test.go @@ -217,7 +217,7 @@ func TestReadClientIntegrationAdditionalFields(t *testing.T) { ConnectTimeout: config.Duration(10 * time.Second), RequestTimeout: config.Duration(1 * time.Second), Workarounds: opcua.OpcUAWorkarounds{}, - OptionalFields: opcua.OpcUAAdditionalFields{IncludeDataType: true}, + OptionalFields: []string{"DataType"}, }, MetricName: "testing", RootNodes: make([]input.NodeSettings, 0), @@ -316,6 +316,8 @@ auth_method = "Anonymous" username = "" password = "" +optional_fields = ["DataType"] + [[inputs.opcua.nodes]] name = "name" namespace = "1" @@ -359,8 +361,6 @@ additional_valid_status_codes = ["0xC0"] [inputs.opcua.request_workarounds] use_unregistered_reads = true -[inputs.opcua.additional_fields] -include_datatype = true ` c := config.NewConfig() @@ -430,7 +430,7 @@ include_datatype = true }, o.ReadClientConfig.Groups) require.Equal(t, opcua.OpcUAWorkarounds{AdditionalValidStatusCodes: []string{"0xC0"}}, o.ReadClientConfig.Workarounds) require.Equal(t, ReadClientWorkarounds{UseUnregisteredReads: true}, o.ReadClientConfig.ReadClientWorkarounds) - require.Equal(t, opcua.OpcUAAdditionalFields{IncludeDataType: true}, o.ReadClientConfig.OptionalFields) + require.Equal(t, []string{"DataType"}, o.ReadClientConfig.OptionalFields) err = o.Init() require.NoError(t, err) require.Len(t, o.client.NodeMetricMapping, 5, "incorrect number of nodes") diff --git a/plugins/inputs/opcua/sample.conf b/plugins/inputs/opcua/sample.conf index cd3565cc0329c..a2b975acdc723 100644 --- a/plugins/inputs/opcua/sample.conf +++ b/plugins/inputs/opcua/sample.conf @@ -43,6 +43,11 @@ ## "source" -- uses the timestamp provided by the source # timestamp = "gather" # + ## Include additional Fields in each metric + ## Available options are: + ## DataType -- OPC-UA Data Type (string) + # optional_fields = [] + # ## Node ID configuration ## name - field name to use in the output ## namespace - OPC UA namespace of the node (integer value 0 thru 3) @@ -123,11 +128,6 @@ # identifier_type = "" # identifier = "" - ## Include additional Fields in each metric - # [inputs.opcua.additional_fields] - ## OPC-UA DataType - # include_datatype = false - ## Enable workarounds required by some devices to work correctly # [inputs.opcua.workarounds] ## Set additional valid status codes, StatusOK (0x0) is always considered valid diff --git a/plugins/inputs/opcua_listener/README.md b/plugins/inputs/opcua_listener/README.md index 4ab0ceb73a10b..c8c266bf891d3 100644 --- a/plugins/inputs/opcua_listener/README.md +++ b/plugins/inputs/opcua_listener/README.md @@ -91,6 +91,11 @@ to use them. # e.g.: json_timestamp_format = "2006-01-02T15:04:05Z07:00" #timestamp_format = "" # + ## Include additional Fields in each metric + ## Available options are: + ## DataType -- OPC-UA Data Type (string) + # optional_fields = [] + # ## Node ID configuration ## name - field name to use in the output ## namespace - OPC UA namespace of the node (integer value 0 thru 3) @@ -227,11 +232,6 @@ to use them. # deadband_value = 0.0 # - ## Include additional Fields in each metric - # [inputs.opcua.additional_fields] - ## OPC-UA DataType - # include_datatype = false - ## Enable workarounds required by some devices to work correctly # [inputs.opcua_listener.workarounds] ## Set additional valid status codes, StatusOK (0x0) is always considered valid @@ -261,7 +261,7 @@ This node configuration produces a metric like this: opcua,id=ns\=3;s\=Temperature temp=79.0,Quality="OK (0x0)" 1597820490000000000 ``` -With 'DataType' enabled in Additional Metrics, this node configuration +With 'DataType' entered in Additional Metrics, this node configuration produces a metric like this: ```text diff --git a/plugins/inputs/opcua_listener/opcua_listener_test.go b/plugins/inputs/opcua_listener/opcua_listener_test.go index ced7023bdd0e9..654a3e0c4990b 100644 --- a/plugins/inputs/opcua_listener/opcua_listener_test.go +++ b/plugins/inputs/opcua_listener/opcua_listener_test.go @@ -222,7 +222,7 @@ func TestSubscribeClientIntegrationAdditionalFields(t *testing.T) { ConnectTimeout: config.Duration(10 * time.Second), RequestTimeout: config.Duration(1 * time.Second), Workarounds: opcua.OpcUAWorkarounds{}, - OptionalFields: opcua.OpcUAAdditionalFields{IncludeDataType: true}, + OptionalFields: []string{"DataType"}, }, MetricName: "testing", RootNodes: make([]input.NodeSettings, 0), @@ -258,6 +258,7 @@ func TestSubscribeClientIntegrationAdditionalFields(t *testing.T) { if fieldName != tag.Name { continue } + // nil-value tags should not be sent from server, error if one does if tag.Want == nil { t.Errorf("Tag: %s has value: %v", tag.Name, fieldValue) return @@ -308,6 +309,9 @@ auth_method = "Anonymous" timestamp_format = "2006-01-02T15:04:05Z07:00" username = "" password = "" + +optional_fields = ["DataType"] + nodes = [ {name="name", namespace="1", identifier_type="s", identifier="one"}, {name="name2", namespace="2", identifier_type="s", identifier="two"}, @@ -329,9 +333,6 @@ nodes = [{name="name4", identifier="4000", tags=[["tag1", "override"]]}] [inputs.opcua_listener.workarounds] additional_valid_status_codes = ["0xC0"] - -[inputs.opcua_listener.additional_fields] -include_datatype = true ` c := config.NewConfig() @@ -394,7 +395,7 @@ include_datatype = true }, }, o.SubscribeClientConfig.Groups) require.Equal(t, opcua.OpcUAWorkarounds{AdditionalValidStatusCodes: []string{"0xC0"}}, o.SubscribeClientConfig.Workarounds) - require.Equal(t, opcua.OpcUAAdditionalFields{IncludeDataType: true}, o.SubscribeClientConfig.OptionalFields) + require.Equal(t, []string{"DataType"}, o.SubscribeClientConfig.OptionalFields) } func TestSubscribeClientConfigWithMonitoringParams(t *testing.T) { diff --git a/plugins/inputs/opcua_listener/sample.conf b/plugins/inputs/opcua_listener/sample.conf index 65ed1e73352e0..3e333020cafc0 100644 --- a/plugins/inputs/opcua_listener/sample.conf +++ b/plugins/inputs/opcua_listener/sample.conf @@ -52,6 +52,11 @@ # e.g.: json_timestamp_format = "2006-01-02T15:04:05Z07:00" #timestamp_format = "" # + ## Include additional Fields in each metric + ## Available options are: + ## DataType -- OPC-UA Data Type (string) + # optional_fields = [] + # ## Node ID configuration ## name - field name to use in the output ## namespace - OPC UA namespace of the node (integer value 0 thru 3) @@ -188,11 +193,6 @@ # deadband_value = 0.0 # - ## Include additional Fields in each metric - # [inputs.opcua.additional_fields] - ## OPC-UA DataType - # include_datatype = false - ## Enable workarounds required by some devices to work correctly # [inputs.opcua_listener.workarounds] ## Set additional valid status codes, StatusOK (0x0) is always considered valid From 39babfd559388347255ebd6c5110e29c2900890c Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Mon, 4 Dec 2023 17:15:58 -0800 Subject: [PATCH 08/18] Update plugins/inputs/opcua_listener/opcua_listener_test.go Co-authored-by: Thomas Casteleyn --- plugins/inputs/opcua_listener/opcua_listener_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/inputs/opcua_listener/opcua_listener_test.go b/plugins/inputs/opcua_listener/opcua_listener_test.go index 654a3e0c4990b..b45c733fedc7d 100644 --- a/plugins/inputs/opcua_listener/opcua_listener_test.go +++ b/plugins/inputs/opcua_listener/opcua_listener_test.go @@ -242,8 +242,7 @@ func TestSubscribeClientIntegrationAdditionalFields(t *testing.T) { return o.SetupOptions() == nil }, 5*time.Second, 10*time.Millisecond) - err = o.Connect() - require.NoError(t, err, "Connection failed") + require.NoError(t, o.Connect(), "Connection failed") ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() From 8445703ec226f51028fc47a28d42bdb3ff7d430e Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Mon, 4 Dec 2023 17:16:06 -0800 Subject: [PATCH 09/18] Update plugins/inputs/opcua_listener/opcua_listener_test.go Co-authored-by: Thomas Casteleyn --- plugins/inputs/opcua_listener/opcua_listener_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/inputs/opcua_listener/opcua_listener_test.go b/plugins/inputs/opcua_listener/opcua_listener_test.go index b45c733fedc7d..8846653f7fed5 100644 --- a/plugins/inputs/opcua_listener/opcua_listener_test.go +++ b/plugins/inputs/opcua_listener/opcua_listener_test.go @@ -163,8 +163,7 @@ func TestSubscribeClientIntegrationAdditionalFields(t *testing.T) { wait.ForLog("TCP network layer listening on opc.tcp://"), ), } - err := container.Start() - require.NoError(t, err, "failed to start container") + require.NoError(t, container.Start(), "failed to start container") defer container.Terminate() testopctags := []OPCTags{ From 451fc5faffe28211ec7f87822bf77367778e02f5 Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Mon, 4 Dec 2023 17:16:14 -0800 Subject: [PATCH 10/18] Update plugins/inputs/opcua/opcua_test.go Co-authored-by: Thomas Casteleyn --- plugins/inputs/opcua/opcua_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/inputs/opcua/opcua_test.go b/plugins/inputs/opcua/opcua_test.go index c6806e47faff8..77f6ac2649723 100644 --- a/plugins/inputs/opcua/opcua_test.go +++ b/plugins/inputs/opcua/opcua_test.go @@ -232,8 +232,7 @@ func TestReadClientIntegrationAdditionalFields(t *testing.T) { client, err := readConfig.CreateReadClient(testutil.Logger{}) require.NoError(t, err) - err = client.Connect() - require.NoError(t, err, "Connect") + require.NoError(t, client.Connect(), "Connect") actualopcmetrics := []telegraf.Metric{} From e70d48476d9f812e0c319eeeee7f501a2c3b8b2d Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Mon, 4 Dec 2023 17:16:22 -0800 Subject: [PATCH 11/18] Update plugins/inputs/opcua/opcua_test.go Co-authored-by: Thomas Casteleyn --- plugins/inputs/opcua/opcua_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/inputs/opcua/opcua_test.go b/plugins/inputs/opcua/opcua_test.go index 77f6ac2649723..64aeb988f4691 100644 --- a/plugins/inputs/opcua/opcua_test.go +++ b/plugins/inputs/opcua/opcua_test.go @@ -165,8 +165,7 @@ func TestReadClientIntegrationAdditionalFields(t *testing.T) { wait.ForLog("TCP network layer listening on opc.tcp://"), ), } - err := container.Start() - require.NoError(t, err, "failed to start container") + require.NoError(t, container.Start(), "failed to start container") defer container.Terminate() testopctags := []OPCTags{ From f7beaefb9f131adb9c0c6544b4d60e070ac8216e Mon Sep 17 00:00:00 2001 From: Sven Rebhan <36194019+srebhan@users.noreply.github.com> Date: Wed, 6 Dec 2023 22:02:14 +0100 Subject: [PATCH 12/18] Update plugins/inputs/opcua/opcua_test.go Co-authored-by: Thomas Casteleyn --- plugins/inputs/opcua/opcua_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/opcua/opcua_test.go b/plugins/inputs/opcua/opcua_test.go index 64aeb988f4691..afa5d5ec4e836 100644 --- a/plugins/inputs/opcua/opcua_test.go +++ b/plugins/inputs/opcua/opcua_test.go @@ -231,7 +231,7 @@ func TestReadClientIntegrationAdditionalFields(t *testing.T) { client, err := readConfig.CreateReadClient(testutil.Logger{}) require.NoError(t, err) - require.NoError(t, client.Connect(), "Connect") + require.NoError(t, client.Connect()) actualopcmetrics := []telegraf.Metric{} From 917bf3a7da58bf89682e4d1fa14f1102b09f514b Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Wed, 6 Dec 2023 21:40:28 -0500 Subject: [PATCH 13/18] Validate OptionalFields input values --- plugins/common/opcua/client.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/plugins/common/opcua/client.go b/plugins/common/opcua/client.go index 8179538557b5e..cd125cf365c6f 100644 --- a/plugins/common/opcua/client.go +++ b/plugins/common/opcua/client.go @@ -12,6 +12,7 @@ import ( "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/internal/choice" ) type OpcUAWorkarounds struct { @@ -49,7 +50,24 @@ type OpcUAClientConfig struct { } func (o *OpcUAClientConfig) Validate() error { - return o.validateEndpoint() + if err := o.validateOptionalFields(); err != nil { + return err + } + if err := o.validateEndpoint(); err != nil { + return err + } + + return nil +} + +func (o *OpcUAClientConfig) validateOptionalFields() error { + valid_fields := []string{"DataType"} + for _, val := range o.OptionalFields { + if !choice.Contains(val, valid_fields) { + return fmt.Errorf("invalid Optional Field %q", val) + } + } + return nil } func (o *OpcUAClientConfig) validateEndpoint() error { From 9477f0f1e5069f9ab4a0a5a2e34868c5f8c9144d Mon Sep 17 00:00:00 2001 From: Christian Allinson Date: Wed, 6 Dec 2023 21:44:56 -0500 Subject: [PATCH 14/18] Fix lint --- plugins/common/opcua/client.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/common/opcua/client.go b/plugins/common/opcua/client.go index cd125cf365c6f..d0e5b1542445b 100644 --- a/plugins/common/opcua/client.go +++ b/plugins/common/opcua/client.go @@ -53,17 +53,15 @@ func (o *OpcUAClientConfig) Validate() error { if err := o.validateOptionalFields(); err != nil { return err } - if err := o.validateEndpoint(); err != nil { - return err - } - return nil + err := o.validateEndpoint() + return err } func (o *OpcUAClientConfig) validateOptionalFields() error { - valid_fields := []string{"DataType"} + validFields := []string{"DataType"} for _, val := range o.OptionalFields { - if !choice.Contains(val, valid_fields) { + if !choice.Contains(val, validFields) { return fmt.Errorf("invalid Optional Field %q", val) } } From 87e36dc98b3d24d13b0f41821aa3068b0d731ecc Mon Sep 17 00:00:00 2001 From: Joshua Powers Date: Thu, 7 Dec 2023 12:31:52 -0700 Subject: [PATCH 15/18] Update plugins/common/opcua/client.go Co-authored-by: Thomas Casteleyn --- plugins/common/opcua/client.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/common/opcua/client.go b/plugins/common/opcua/client.go index d0e5b1542445b..38ade7b1fdbe8 100644 --- a/plugins/common/opcua/client.go +++ b/plugins/common/opcua/client.go @@ -60,12 +60,7 @@ func (o *OpcUAClientConfig) Validate() error { func (o *OpcUAClientConfig) validateOptionalFields() error { validFields := []string{"DataType"} - for _, val := range o.OptionalFields { - if !choice.Contains(val, validFields) { - return fmt.Errorf("invalid Optional Field %q", val) - } - } - return nil + return choice.CheckSlice(o.OptionalFields, validFields) } func (o *OpcUAClientConfig) validateEndpoint() error { From 31e582b6af2f4183496cfd8c2e040eae59b40017 Mon Sep 17 00:00:00 2001 From: Joshua Powers Date: Thu, 7 Dec 2023 12:32:03 -0700 Subject: [PATCH 16/18] Update plugins/common/opcua/client.go Co-authored-by: Thomas Casteleyn --- plugins/common/opcua/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/common/opcua/client.go b/plugins/common/opcua/client.go index 38ade7b1fdbe8..5545d9b9d0b4d 100644 --- a/plugins/common/opcua/client.go +++ b/plugins/common/opcua/client.go @@ -51,7 +51,7 @@ type OpcUAClientConfig struct { func (o *OpcUAClientConfig) Validate() error { if err := o.validateOptionalFields(); err != nil { - return err + return fmt.Errorf("invalid Optional Field: %w", err) } err := o.validateEndpoint() From 3f8997cfed711e3f8c28131c9dc427bb41007e1e Mon Sep 17 00:00:00 2001 From: Sven Rebhan <36194019+srebhan@users.noreply.github.com> Date: Thu, 7 Dec 2023 20:34:54 +0100 Subject: [PATCH 17/18] Update plugins/common/opcua/client.go --- plugins/common/opcua/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/common/opcua/client.go b/plugins/common/opcua/client.go index 5545d9b9d0b4d..a1a79879a000b 100644 --- a/plugins/common/opcua/client.go +++ b/plugins/common/opcua/client.go @@ -51,7 +51,7 @@ type OpcUAClientConfig struct { func (o *OpcUAClientConfig) Validate() error { if err := o.validateOptionalFields(); err != nil { - return fmt.Errorf("invalid Optional Field: %w", err) + return fmt.Errorf("invalid 'optional_fields': %w", err) } err := o.validateEndpoint() From febe98cf95b9a12d9643fb971176c3f35c0c65a3 Mon Sep 17 00:00:00 2001 From: Sven Rebhan <36194019+srebhan@users.noreply.github.com> Date: Thu, 7 Dec 2023 20:35:02 +0100 Subject: [PATCH 18/18] Update plugins/common/opcua/client.go --- plugins/common/opcua/client.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/common/opcua/client.go b/plugins/common/opcua/client.go index a1a79879a000b..de8c265a2c2d8 100644 --- a/plugins/common/opcua/client.go +++ b/plugins/common/opcua/client.go @@ -54,8 +54,7 @@ func (o *OpcUAClientConfig) Validate() error { return fmt.Errorf("invalid 'optional_fields': %w", err) } - err := o.validateEndpoint() - return err + return o.validateEndpoint() } func (o *OpcUAClientConfig) validateOptionalFields() error {