Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update event-plugin processor with configuration options and examples #341

Merged
merged 4 commits into from
Jan 12, 2024
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
9 changes: 8 additions & 1 deletion docs/user_guide/event_processors/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,16 @@ processors:

### Event processors with cache

When a set of processors are defined under an output where [caching](../outputs/output_intro.md#caching) is enabled, the event messages retried from the cache are processed by each processor at the same time. This allows combining values from different messages together.
In the scenario where processors are configured under an output with [caching](../outputs/output_intro.md#caching) enabled, the event messages retrieved from the cache are processed as a single set by each processor. This concurrent processing facilitates the application of a logic that merges or combines messages, enabling more complex and integrated processing strategies.

### Event processors pipeline

Processors under an output are applied in a strict sequential order for each group of event messages received.

### Event processors plugins

gNMIc incorporates the capability to extend its functionality through the use of event processors as plugins. To integrate seamlessly with gNMIc, these plugins need to be written in Golang.

The communication between gNMIc and these plugins is facilitated by HashiCorp's go-plugin package, which employs `netrpc` as the underlying protocol for this interaction.

See some plugin examples [here](https://github.com/openconfig/gnmic/examples/plugins)
2 changes: 1 addition & 1 deletion examples/plugin/main.go → examples/plugins/demo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"os"

"github.com/hashicorp/go-plugin"

"github.com/openconfig/gnmic/pkg/formatters"
"github.com/openconfig/gnmic/pkg/formatters/event_plugin"

"github.com/openconfig/gnmic/pkg/types"
)

Expand Down
78 changes: 78 additions & 0 deletions examples/plugins/event-add-hostname/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Add hostname processor plugin

`event-add-hostname` is an event processor that gNMIc starts as a plugin. It enriches received gNMI notifications with the collector hostname as a tag.

## Build

To build the plugin run:

```bash
cd examples/plugins/event-add-hostname
go build -o event-add-hostname
```

## Running the plugin

- To run the plugin point gNMIc to the directory where the plugin binary resides. Either using the flag `--plugin-processors-path | -P`:

```bash
gnmic --config gnmic.yaml subscribe -P /path/to/plugin/bin
```

Or using the config file:

```yaml
plugins:
path: /path/to/plugin/bin
glob: "*"
start-timeout: 0s
```

This allows gNMIc to discover the plugin executable and initialize it. Make sure the files gNMIc loads are executable.

- Next configure the plugin as a processor:

```yaml
processors:
proc1:
event-add-hostname:
debug: true
# the tag name to add with the host hostname as a tag value.
hostname-tag-name: "collector-host"
# read-interval controls how often the plugin runs the hostname cmd to get the host hostanme
# by default it's at most every 1 minute
read-interval: 1m
```

The processor type `event-add-hostname` should match the executable filename.

- Then add that processor under an output just like a you would do it with a regular processor:

```yaml
outputs:
out1:
type: file
format: event
event-processors:
- proc1
```

The resulting event message should have a new tag called `collector-host`

```json
[
{
"name": "sub1",
"timestamp": 1704572759243640092,
"tags": {
"collector-host": "kss",
"interface_name": "ethernet-1/1",
"source": "clab-ex-srl1",
"subscription-name": "sub1"
},
"values": {
"/srl_nokia-interfaces:interface/statistics/out-octets": "4105346"
}
}
]
```
136 changes: 136 additions & 0 deletions examples/plugins/event-add-hostname/event-add-hostname.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package main

import (
"bytes"
"log"
"os"
"os/exec"
"sync"
"time"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"

"github.com/openconfig/gnmic/pkg/formatters"
"github.com/openconfig/gnmic/pkg/formatters/event_plugin"
"github.com/openconfig/gnmic/pkg/types"
)

const (
processorType = "event-add-hostname"
loggingPrefix = "[" + processorType + "] "
hostnameCmd = "hostname"
)

type addHostnameProcessor struct {
Debug bool `mapstructure:"debug,omitempty" yaml:"debug,omitempty" json:"debug,omitempty"`
ReadInterval time.Duration `mapstructure:"read-interval,omitempty" yaml:"read-interval,omitempty" json:"read-interval,omitempty"`
HostnameTagName string `mapstructure:"hostname-tag-name,omitempty" yaml:"hostname-tag-name,omitempty" json:"hostname-tag-name,omitempty"`

m *sync.RWMutex
hostname string
lastRead time.Time

targetsConfigs map[string]*types.TargetConfig
actionsDefinitions map[string]map[string]interface{}
processorsDefinitions map[string]map[string]any
logger hclog.Logger
}

func (p *addHostnameProcessor) Init(cfg interface{}, opts ...formatters.Option) error {
err := formatters.DecodeConfig(cfg, p)
if err != nil {
return err
}

p.setupLogger()
if p.ReadInterval <= 0 {
p.ReadInterval = time.Minute
}
if p.HostnameTagName == "" {
p.HostnameTagName = "collector-hostname"
}
return p.readHostname()
}

func (p *addHostnameProcessor) Apply(event ...*formatters.EventMsg) []*formatters.EventMsg {
p.m.Lock()
defer p.m.Unlock()

err := p.readHostname()
if err != nil {
p.logger.Error("failed to read hostname", "error", err)
}
for _, e := range event {
if e.Tags == nil {
e.Tags = make(map[string]string)
}
e.Tags[p.HostnameTagName] = p.hostname
}
return event
}

func (p *addHostnameProcessor) WithActions(act map[string]map[string]interface{}) {
p.actionsDefinitions = act
}

func (p *addHostnameProcessor) WithTargets(tcs map[string]*types.TargetConfig) {
p.targetsConfigs = tcs
}

func (p *addHostnameProcessor) WithProcessors(procs map[string]map[string]any) {
p.processorsDefinitions = procs
}

func (p *addHostnameProcessor) WithLogger(l *log.Logger) {
}

func (p *addHostnameProcessor) setupLogger() {
p.logger = hclog.New(&hclog.LoggerOptions{
Output: os.Stderr,
TimeFormat: "2006/01/02 15:04:05.999999",
})
if p.Debug {
p.logger.SetLevel(hclog.Debug)
}
}

func (p *addHostnameProcessor) readHostname() error {
now := time.Now()
if p.lastRead.After(now.Add(-p.ReadInterval)) {
return nil
}
//
cmd := exec.Command(hostnameCmd)
out, err := cmd.Output()
if err != nil {
return err
}
p.hostname = string(bytes.TrimSpace(out))
p.lastRead = now
return nil
}

func main() {
logger := hclog.New(&hclog.LoggerOptions{
Output: os.Stderr,
DisableTime: true,
})

logger.Info("starting plugin processor", "name", processorType)

plug := &addHostnameProcessor{
m: new(sync.RWMutex),
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "GNMIC_PLUGIN",
MagicCookieValue: "gnmic",
},
Plugins: map[string]plugin.Plugin{
processorType: &event_plugin.EventProcessorPlugin{Impl: plug},
},
Logger: logger,
})
}
128 changes: 128 additions & 0 deletions examples/plugins/event-gnmi-get/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# gNMI Get based notification enriching processor plugin

`event-gnmi-get` is an event processor that gNMIc starts as a plugin. It enriches received gNMI notifications with tags retrieved using a gNMI Get RPC.

## Building the plugin

```bash
cd examples/plugins/event-gnmi-get
go build -o event-gnmi-get
```

## Running the plugin

- To run the plugin point gNMIc to the directory where the plugin binary resides. Either using the flag `--plugin-processors-path | -P`:

```bash
gnmic --config gnmic.yaml subscribe -P /path/to/plugin/bin
```

Or using the config file:

```yaml
plugins:
path: /path/to/plugin/bin
glob: "*"
start-timeout: 0s
```

This allows gNMIc to discover the plugin executable and initialize it. Make sure the files gNMIc loads are executable.

- Next configure the plugin as a processor:

```yaml
processors:
proc2:
event-gnmi-get:
debug: true
encoding: ascii
data-type: all
paths:
- path: "platform/chassis/type"
tag-name: "chassis-type"
- path: "platform/chassis/hw-mac-address"
tag-name: "hw-mac-address"
- path: "system/name/host-name"
tag-name: "hostname"
```

The processor type `event-gnmi-get` should match the executable filename.

- Then add that processor under an output just like a you would do it with a regular processor:

```yaml
outputs:
out1:
type: file
format: event
event-processors:
- proc2
```

The resulting event message should have a set of new tags called `chassis-type`, `hw-mac-address` and `hostname`.

```json
[
{
"name": "sub1",
"timestamp": 1704573345190497607,
"tags": {
"chassis-type": "7220 IXR-D2",
"hostname": "srl1",
"interface_name": "ethernet-1/1",
"hw-mac-address": "1A:F2:00:FF:00:00",
"source": "clab-ex-srl1",
"subscription-name": "sub1"
},
"values": {
"/srl_nokia-interfaces:interface/statistics/out-octets": "4108666"
}
}
]
```

## Examples

### trigger get request directly to the node

```yaml
processors:
proc1:
event-gnmi-get:
debug: true
encoding: ascii
data-type: all
paths:
- path: "platform/chassis/type"
tag-name: "chassis-type"
- path: "platform/chassis/hw-mac-address"
tag-name: "hw-mac-address"
- path: "system/name/host-name"
tag-name: "hostname"
```

### trigger get request through gNMIc's gNMI server

```yaml
# enable gNMIc gNMI server
gnmi-server:
address: :57401

processors:
proc1:
event-gnmi-get:
debug: true
encoding: ascii
data-type: all
# set the gNMI Get target to the local gNMI server address
target: localhost:57401
# include the actual target name in the GetRequest Prefix
prefix-target: '{{ index .Tags "source" }}'
paths:
- path: "platform/chassis/type"
tag-name: "chassis-type"
- path: "platform/chassis/hw-mac-address"
tag-name: "hw-mac-address"
- path: "system/name/host-name"
tag-name: "hostname"
```
Loading