Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .chloggen/yanggrpc_wip.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
component: receiver/yang_grpc

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Support collecting any metric by browsing the whole metrics tree

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [47054]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
213 changes: 70 additions & 143 deletions receiver/yanggrpcreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,117 +18,60 @@ The YANG/gRPC receiver receives metrics offered using the
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
<!-- end autogenerated section -->

This receiver exposes a gRPC endpoint, accepting the gRPC configuration offered by the [configgrpc](https://pkg.go.dev/go.opentelemetry.io/collector/config/configgrpc) module.
The **YANG gRPC Receiver** collects Model-Driven Telemetry (MDT) from network devices (primarily Cisco) via gRPC Dial-out. It transforms complex Cisco KV-GPB (Key-Value Google Protocol Buffers) data into standard OpenTelemetry metrics, specifically optimized for high-performance analysis in **Splunk**.

# Configuration
## Key Features

## Definition
- **Context-Aware Processing**: Automatically discovers dimensions (labels) like Interface names, VRF IDs, or BGP neighbors by traversing the telemetry tree.
- **YANG-Driven Mapping**: Uses Cisco YANG models to distinguish between Counters (monotonic sums) and Gauges (instantaneous values).
- **Smart Fallback**: Works out-of-the-box using naming heuristics (detecting keys like `name`, `id`, `address`) even if local YANG files are not provided.
- **OTLP Compliance**: Normalizes all Cisco numeric types into `float64` and handles string values as descriptive `_info` metrics.
- **Security Hardening**: Includes built-in support for client IP allow-listing and ingestion rate limiting.

### gRPC Configuration
---

See [configgrpc](https://pkg.go.dev/go.opentelemetry.io/collector/config/configgrpc).
### Example Configuration

The default configuration sets up the following parameters:
Add this receiver to your OpenTelemetry Collector configuration:

```yaml
endpoint: localhost:57500
transport: tcp
keepalive:
server_parameters:
time: 30s
timeout: 10s
max_concurrent_streams: 100
max_recv_msg_size_mib: 4
receivers:
yang_grpc:
endpoint: "0.0.0.0:57000"
yang:
module_paths:
- "/etc/otelcol/yang/cisco/xe"
- "/etc/otelcol/yang/standard/ietf"
security:
allowed_clients: ["10.0.0.0/8", "192.168.1.0/24"]
rate_limiting:
enabled: true
requests_per_second: 100
burst_size: 50

exporters:
splunk_hec:
endpoint: "https://your-splunk-instance:8088/services/collector"
token: "${SPLUNK_HEC_TOKEN}"
index: "network_metrics"

service:
pipelines:
metrics:
receivers: [yang_grpc]
processors: [batch]
exporters: [splunk_hec]
```

### Security Configuration (`security`)

#### Rate Limiting (`rate_limiting`)

##### `enabled`
- **Type**: `bool`
- **Default**: `false`
- **Description**: Enable per-client rate limiting

##### `requests_per_second`
- **Type**: `float64`
- **Default**: `100.0`
- **Description**: Maximum requests per second per client

##### `burst_size`
- **Type**: `int`
- **Default**: `10`
- **Description**: Burst allowance for rate limiting

##### `cleanup_interval`
- **Type**: `duration`
- **Default**: `1m`
- **Description**: How often to clean up rate limiter entries

#### Access Control

##### `allowed_clients`
- **Type**: `[]string`
- **Default**: `[]` (allow all)
- **Description**: IP addresses or CIDR blocks allowed to connect
- **Examples**:
```yaml
allowed_clients:
- "10.1.1.100" # Specific IP
- "192.168.0.0/16" # CIDR block
- "10.0.0.0/8" # Large network
```

##### `max_connections`
- **Type**: `int`
- **Default**: `1000`
- **Description**: Maximum concurrent connections

##### `connection_timeout`
- **Type**: `duration`
- **Default**: `30s`
- **Description**: Timeout for new connections

##### `enable_metrics`
- **Type**: `bool`
- **Default**: `true`
- **Description**: Enable security-related metrics collection

## Performance Tuning

### Keep-Alive Configuration (`keep_alive`)
---

#### `time`
- **Type**: `duration`
- **Default**: `30s`
- **Description**: Time between keep-alive pings

#### `timeout`
- **Type**: `duration`
- **Default**: `10s`
- **Description**: Timeout waiting for keep-alive response

## YANG Parser Settings

### YANG Configuration (`yang`)

#### `enable_rfc_parser`
- **Type**: `bool`
- **Default**: `true`
- **Description**: Enable RFC 6020/7950 compliant YANG parser

#### `cache_modules`
- **Type**: `bool`
- **Default**: `true`
- **Description**: Cache discovered YANG modules
# Configuration

#### `max_modules`
- **Type**: `int`
- **Default**: `1000`
- **Description**: Maximum number of YANG modules to cache
## gRPC Configuration

## Default
See [configgrpc](https://pkg.go.dev/go.opentelemetry.io/collector/config/configgrpc).

## Default Configuration
```yaml
yang_grpc:
endpoint: localhost:57500
Expand All @@ -139,63 +82,47 @@ yang_grpc:
timeout: 10s
max_concurrent_streams: 100
max_recv_msg_size_mib: 4
security:
connection_timeout: 30s
enable_metrics: true
rate_limiting:
burst_size: 10
cleanup_interval: 1m0s
enabled: false
requests_per_second: 100
yang:
cache_modules: true
enable_rfc_parser: true
max_modules: 1000
```

## Production
## Security Configuration (security)
* `rate_limiting`: enabled (default: false), requests_per_second (100.0), burst_size (10).
* `access_control`: allowed_clients (list of IP/CIDR), max_connections (1000).

```yaml
## YANG Parser Settings (yang)
* `enable_rfc_parser`: Enable RFC 6020/7950 compliant parsing.
* `cache_modules`: Local directories containing Cisco/IETF `.yang` files for accurate parsing. Cache discovered YANG modules to reduce CPU overhead.

## Production Deployment Example
```YAML
yang_grpc:
endpoint: "0.0.0.0:57500"
# TLS/mTLS Configuration
tls:
enabled: true
cert_file: "/etc/otel/certs/server.crt"
key_file: "/etc/otel/certs/server.key"
ca_file: "/etc/otel/certs/ca.crt"
client_auth_type: "RequireAndVerifyClientCert"
min_version: "1.2"
max_version: "1.3"
reload_interval: 5m

# Security & Rate Limiting
security:
rate_limiting:
enabled: true
requests_per_second: 100.0
burst_size: 10
cleanup_interval: 1m
allowed_clients:
- "10.0.0.0/8"
- "192.168.0.0/16"
max_connections: 1000
connection_timeout: 30s
enable_metrics: true

# Performance Settings
max_concurrent_streams: 100
max_recv_msg_size_mib: 4

# Keep-Alive Configuration
keepalive:
server_parameters:
time: 30s
timeout: 10s

# YANG Parser Configuration
yang:
enable_rfc_parser: true
cache_modules: true
max_modules: 1000
yang:
cache_modules: true
```

---

# Example OTLP Output
When processing Cisco ARP telemetry data, the receiver generates structured OTLP metrics:

### Metric Name: cisco.content.arp-oper.type_info

Value: 1.0 (Info Metric)

### Attributes:

* interface: Vlan200
* vrf: Default
* address: 10.10.10.1
* yang.module: Cisco-IOS-XE-arp-oper
* cisco.node_id: Switch-Core-01
* cisco.subscription_id: 112
13 changes: 12 additions & 1 deletion receiver/yanggrpcreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ type YANGConfig struct {

// MaxModules is the maximum number of YANG modules to cache
MaxModules int `mapstructure:"max_modules"`

// ModulePaths defines the directories where .yang files are stored.
// This is used by the internal parser to resolve Cisco-specific schemas.
ModulePaths []string `mapstructure:"module_paths"`
}

// Config defines configuration for yanggrpc receiver.
Expand All @@ -77,14 +81,21 @@ type Config struct {
Security SecurityConfig `mapstructure:"security"`
}

// Validate checks the receiver configuration is valid
// Validate checks the receiver configuration is valid.
func (c *Config) Validate() error {
// Validate the base gRPC server configuration (endpoint, TLS, etc.)
if err := c.ServerConfig.Validate(); err != nil {
return err
}

// Validate security settings
if err := c.Security.Validate(); err != nil {
return err
}

// Optional: You could add a check here to ensure ModulePaths aren't empty
// if EnableRFCParser is true, but since we have a "fallback" logic
// in grpc_service.go, it's better to keep it optional.

return nil
}
5 changes: 5 additions & 0 deletions receiver/yanggrpcreceiver/config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ $defs:
max_modules:
description: MaxModules is the maximum number of YANG modules to cache
type: integer
module_paths:
description: ModulePaths defines the directories where .yang files are stored. This is used by the internal parser to resolve Cisco-specific schemas.
type: array
items:
type: string
description: Config defines configuration for yanggrpc receiver.
type: object
properties:
Expand Down
13 changes: 4 additions & 9 deletions receiver/yanggrpcreceiver/detailed_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,9 @@ func TestDetailedTelemetryValidation(t *testing.T) {
}

ctx := t.Context()
rcvr, err := createMetricsReceiver(ctx, settings, config, csmr)
if err != nil {
t.Fatalf("Failed to create receiver: %v", err)
}
rcvr := createMetricsReceiver(ctx, settings, config, csmr)

err = rcvr.Start(ctx, componenttest.NewNopHost())
err := rcvr.Start(ctx, componenttest.NewNopHost())
if err != nil {
t.Fatalf("Failed to start receiver: %v", err)
}
Expand Down Expand Up @@ -129,9 +126,7 @@ func TestDetailedTelemetryValidation(t *testing.T) {

for _, tc := range testCases {
err := sendDetailedTelemetryData("localhost:57403", "CISCO-TEST-SWITCH", tc.interfaceName, tc.rxPkts, tc.txPkts, tc.rxBytes, tc.txBytes)
if err != nil {
t.Fatalf("Failed to send telemetry for %s: %v", tc.name, err)
}
assert.NoError(t, err, "Failed to send telemetry for %s: %v", tc.name, err)
}

// Wait for data processing
Expand Down Expand Up @@ -409,7 +404,7 @@ func sendDetailedTelemetryData(endpoint, nodeID, interfaceName string, rxPkts, t
}

_, err = stream.Recv()
if err != nil && errors.Is(err, io.EOF) {
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("unexpected error receiving response: %w", err)
}

Expand Down
6 changes: 5 additions & 1 deletion receiver/yanggrpcreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
package yanggrpcreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/yanggrpcreceiver"

import (
"context"
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"

Expand All @@ -18,7 +20,9 @@ func NewFactory() receiver.Factory {
return xreceiver.NewFactory(
metadata.Type,
createDefaultConfig,
xreceiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability),
xreceiver.WithMetrics(func(ctx context.Context, settings receiver.Settings, config component.Config, metrics consumer.Metrics) (receiver.Metrics, error) {
return createMetricsReceiver(ctx, settings, config, metrics), nil
}, metadata.MetricsStability),
xreceiver.WithDeprecatedTypeAlias(metadata.DeprecatedType),
)
}
Expand Down
Loading
Loading