Skip to content

OPC-UA: Support wildcard patterns for node discovery via Browse service #18529

@skartikey

Description

@skartikey

Feature Request

Proposal to add browse-based node discovery for the OPC-UA input plugins (inputs.opcua and inputs.opcua_listener), allowing users to specify glob patterns over browse paths instead of enumerating every node explicitly. This is one of the most requested features for large-scale OPC-UA deployments.

Addresses #8275

Motivation

Users managing factories or industrial environments often have thousands of OPC-UA nodes (e.g., MV01..MV99 across dozens of devices). Today, each node must be listed individually in the config:

[[inputs.opcua.group.nodes]]
  name = "host_MV01"
  namespace = "2"
  identifier_type = "s"
  identifier = "Plant.Device1.MV01"

[[inputs.opcua.group.nodes]]
  name = "host_MV02"
  namespace = "2"
  identifier_type = "s"
  identifier = "Plant.Device1.MV02"

# ... repeated hundreds of times

This is error-prone, hard to maintain, and doesn't adapt when new nodes appear on the server.

Proposed Configuration

Existing nodes and group config remains fully backwards compatible. A new [inputs.opcua.browse] section enables pattern-based discovery:

[[inputs.opcua]]
  endpoint = "opc.tcp://factory-server:4840"
  name = "opcua"

  # Existing explicit nodes still work
  [[inputs.opcua.group.nodes]]
    name = "server_time"
    namespace = "0"
    identifier_type = "i"
    identifier = "2258"

  # NEW: Browse-based discovery
  [inputs.opcua.browse]
    # Starting node for browsing (default: Objects folder ns=0;i=85)
    # browse_root = "ns=0;i=85"

    # Max tree depth to prevent runaway traversal (default: 0 = unlimited)
    # browse_depth = 10

    # Max total discovered nodes as a safety limit (default: 10000)
    # browse_max_nodes = 10000

    # Node classes to match as leaf nodes (default: ["Variable"])
    # browse_node_classes = ["Variable"]

    [[inputs.opcua.browse.browse_path]]
      # Glob pattern over browse path segments
      # Supports: * (any single segment), ** (recursive descent),
      # ? (single char), [abc] (character class)
      pattern = "Objects/Plant1/*/MV*"
      name = "motor_valves"

      # How to derive the metric field name:
      # "browse_name" (default), "display_name", or "path"
      # field_name_source = "browse_name"

      [inputs.opcua.browse.browse_path.default_tags]
        plant = "plant1"

      # Extract browse path segments as tag values (key = tag name, value = segment index)
      [inputs.opcua.browse.browse_path.tags_from_path]
        device = 2  # "Objects/Plant1/Device1/MV01" -> device="Device1"

    [[inputs.opcua.browse.browse_path]]
      pattern = "Objects/**/Temperature"
      name = "temperatures"

Pattern Examples

Pattern Matches
Objects/Plant1/*/MV* Any MV-prefixed Variable one level under Plant1 children
Objects/**/Temperature Temperature nodes at any depth under Objects
Objects/Plant[12]/Device?/Status Status under Device + single char, under Plant1 or Plant2
Objects/*/Sensors/* All sensor values across all top-level children

Design Approach

Browse, Don't Glob Strings

OPC-UA's address space is a graph, not a flat namespace. Node IDs are opaque identifiers that can't be pattern-matched without first knowing what exists. The implementation uses the standard OPC-UA Browse service to walk the address space, then matches the resulting browse paths against user patterns.

Preserve Hierarchy as a Tree

The browse result is maintained as a tree (BrowsedNode with Children) until the final metric-mapping step. This avoids:

  • Losing parent-child context needed for path-based filtering
  • Expensive re-browsing when multiple patterns match overlapping subtrees
  • Ambiguity from premature path flattening

Segment-Level Glob Matching (Not Regex)

Patterns operate on browse path segments (split by /), not on flattened strings. This aligns with the tree structure and is more intuitive for operators. Go's path.Match handles per-segment *, ?, [class]; custom logic handles the ** recursive wildcard.

Feed Into Existing Pipeline

Discovered nodes are converted to NodeSettings and appended to the existing RootNodes list before InitNodeMetricMapping() runs. All downstream code (read client, subscribe client, metric generation, node registration) works unchanged.

Cycle Detection

OPC-UA address spaces can be cyclic. The BFS traversal tracks visited node IDs to avoid infinite loops.

Batched Browse Requests

The gopcua Browse API accepts multiple BrowseDescription entries per request. The implementation batches children lookups to reduce round-trips for large address spaces.

Implementation Plan

Phase 1

  1. Path matcher (plugins/common/opcua/pathmatch.go) - glob matching over []string segments
  2. Address space browser (plugins/common/opcua/browse.go) - BFS traversal with cycle detection, depth/node limits, batched Browse requests
  3. Browse resolver (plugins/common/opcua/input/browse_resolver.go) - convert matched nodes to NodeSettings with field name derivation and tag extraction
  4. Integration - wire browse into readClient.connect() and subscribeClient.connect()
  5. Config, sample.conf, README

Phase 2 (future)

  • browse_refresh_interval - periodic re-browsing to discover new nodes without restart
  • TranslateBrowsePathsToNodeIDs optimization for known path prefixes

Safety Mechanisms

  • browse_max_nodes prevents OOM on huge address spaces (default: 10,000)
  • browse_depth prevents deep traversal (default: unlimited, but recommended)
  • Cycle detection via visited-node tracking during BFS
  • Per-node browse errors are logged but don't fail the entire discovery
  • Zero matches produce a warning, not an error (server may not have those nodes yet)
  • Existing validateNodeToAdd detects duplicate nodes from overlapping patterns

Compatibility

  • Fully backwards compatible: no changes to existing nodes/group config behavior
  • Users can mix explicit nodes and browse patterns
  • Works with both inputs.opcua (polling) and inputs.opcua_listener (subscription)

References

Metadata

Metadata

Assignees

Labels

area/opcuafeature requestRequests for new plugin and for new features to existing plugins

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions