Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
95
plugins/inputs/p4runtime/README.md
Normal file
95
plugins/inputs/p4runtime/README.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
# P4 Runtime Input Plugin
|
||||
|
||||
P4 is a language for programming the data plane of network devices,
|
||||
such as Programmable Switches or Programmable Network Interface Cards.
|
||||
The P4Runtime API is a control plane specification to manage
|
||||
the data plane elements of those devices dynamically by a P4 program.
|
||||
|
||||
The `p4runtime` plugin gathers metrics about `Counter` values
|
||||
present in P4 Program loaded onto networking device.
|
||||
Metrics are collected through gRPC connection with
|
||||
[P4Runtime](https://github.com/p4lang/p4runtime) server.
|
||||
|
||||
P4Runtime Plugin uses `PkgInfo.Name` field.
|
||||
If user wants to gather information about program name, please follow
|
||||
[6.2.1. Annotating P4 code with PkgInfo] instruction and apply changes
|
||||
to your P4 program.
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# P4Runtime telemetry input plugin
|
||||
[[inputs.p4runtime]]
|
||||
## Define the endpoint of P4Runtime gRPC server to collect metrics.
|
||||
# endpoint = "127.0.0.1:9559"
|
||||
## Set DeviceID required for Client Arbitration.
|
||||
## https://p4.org/p4-spec/p4runtime/main/P4Runtime-Spec.html#sec-client-arbitration-and-controller-replication
|
||||
# device_id = 1
|
||||
## Filter counters by their names that should be observed.
|
||||
## Example: counter_names_include=["ingressCounter", "egressCounter"]
|
||||
# counter_names_include = []
|
||||
|
||||
## Optional TLS Config.
|
||||
## Enable client-side TLS and define CA to authenticate the device.
|
||||
# enable_tls = false
|
||||
# tls_ca = "/etc/telegraf/ca.crt"
|
||||
## Set minimal TLS version to accept by the client.
|
||||
# tls_min_version = "TLS12"
|
||||
## Use TLS but skip chain & host verification.
|
||||
# insecure_skip_verify = true
|
||||
|
||||
## Define client-side TLS certificate & key to authenticate to the device.
|
||||
# tls_cert = "/etc/telegraf/client.crt"
|
||||
# tls_key = "/etc/telegraf/client.key"
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
P4Runtime gRPC server communicates using [p4runtime.proto] Protocol Buffer.
|
||||
Static information about P4 program loaded into programmable switch
|
||||
are collected by `GetForwardingPipelineConfigRequest` message.
|
||||
Plugin gathers dynamic metrics with `Read` method.
|
||||
`Readrequest` is defined with single `Entity` of type `CounterEntry`.
|
||||
Since P4 Counter is array, plugin collects values of every cell of array
|
||||
by [wildcard query].
|
||||
|
||||
Counters defined in P4 Program have unique ID and name.
|
||||
Counters are arrays, thus `counter_index` informs
|
||||
which cell value of array is described in metric.
|
||||
|
||||
Tags are constructed in given manner:
|
||||
|
||||
- `p4program_name`: P4 program name provided by user.
|
||||
If user wants to gather information about program name, please follow
|
||||
[6.2.1. Annotating P4 code with PkgInfo] instruction and apply changes
|
||||
to your P4 program.
|
||||
- `counter_name`: Name of given counter in P4 program.
|
||||
- `counter_type`: Type of counter (BYTES, PACKETS, BOTH).
|
||||
|
||||
Fields are constructed in given manner:
|
||||
|
||||
- `bytes`: Number of bytes gathered in counter.
|
||||
- `packets` Number of packets gathered in counter.
|
||||
- `counter_index`: Index at which metrics are collected in P4 counter.
|
||||
|
||||
## Example Output
|
||||
|
||||
Expected output for p4runtime plugin instance
|
||||
running on host named `p4runtime-host`:
|
||||
|
||||
```text
|
||||
p4_runtime,counter_name=MyIngress.egressTunnelCounter,counter_type=BOTH,host=p4 bytes=408i,packets=4i,counter_index=200i 1675175030000000000
|
||||
```
|
||||
|
||||
[6.2.1. Annotating P4 code with PkgInfo]: https://p4.org/p4-spec/p4runtime/main/P4Runtime-Spec.html#sec-annotating-p4-code-with-pkginfo
|
||||
[p4runtime.proto]: https://github.com/p4lang/p4runtime/blob/main/proto/p4/v1/p4runtime.proto
|
||||
[wildcard query]: https://github.com/p4lang/p4runtime/blob/main/proto/p4/v1/p4runtime.proto#L379
|
232
plugins/inputs/p4runtime/p4runtime.go
Normal file
232
plugins/inputs/p4runtime/p4runtime.go
Normal file
|
@ -0,0 +1,232 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package p4runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
p4_config "github.com/p4lang/p4runtime/go/p4/config/v1"
|
||||
p4 "github.com/p4lang/p4runtime/go/p4/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
common_tls "github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
const (
|
||||
defaultDeviceID = 1
|
||||
defaultEndpoint = "127.0.0.1:9559"
|
||||
)
|
||||
|
||||
type P4runtime struct {
|
||||
Endpoint string `toml:"endpoint"`
|
||||
DeviceID uint64 `toml:"device_id"`
|
||||
CounterNamesInclude []string `toml:"counter_names_include"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
EnableTLS bool `toml:"enable_tls"`
|
||||
common_tls.ClientConfig
|
||||
|
||||
conn *grpc.ClientConn
|
||||
client p4.P4RuntimeClient
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func (*P4runtime) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (p *P4runtime) Init() error {
|
||||
if p.Endpoint == "" {
|
||||
p.Log.Debugf("Using default Endpoint: %v", defaultEndpoint)
|
||||
p.Endpoint = defaultEndpoint
|
||||
}
|
||||
|
||||
return p.newP4RuntimeClient()
|
||||
}
|
||||
|
||||
func (p *P4runtime) Gather(acc telegraf.Accumulator) error {
|
||||
p4Info, err := p.getP4Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(p4Info.Counters) == 0 {
|
||||
p.Log.Warn("No counters available in P4 Program!")
|
||||
return nil
|
||||
}
|
||||
|
||||
filteredCounters := filterCounters(p4Info.Counters, p.CounterNamesInclude)
|
||||
if len(filteredCounters) == 0 {
|
||||
p.Log.Warn("No filtered counters available in P4 Program!")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, counter := range filteredCounters {
|
||||
p.wg.Add(1)
|
||||
go func(counter *p4_config.Counter) {
|
||||
defer p.wg.Done()
|
||||
entries, err := p.readAllEntries(counter.Preamble.Id)
|
||||
if err != nil {
|
||||
acc.AddError(
|
||||
fmt.Errorf(
|
||||
"reading counter entries with ID=%v failed with error: %w",
|
||||
counter.Preamble.Id,
|
||||
err,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
ce := entry.GetCounterEntry()
|
||||
|
||||
if ce == nil {
|
||||
acc.AddError(fmt.Errorf("reading counter entry from entry %v failed", entry))
|
||||
continue
|
||||
}
|
||||
|
||||
if ce.Data.ByteCount == 0 && ce.Data.PacketCount == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"bytes": ce.Data.ByteCount,
|
||||
"packets": ce.Data.PacketCount,
|
||||
"counter_index": ce.Index.Index,
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"p4program_name": p4Info.PkgInfo.Name,
|
||||
"counter_name": counter.Preamble.Name,
|
||||
"counter_type": counter.Spec.Unit.String(),
|
||||
}
|
||||
|
||||
acc.AddFields("p4_runtime", fields, tags)
|
||||
}
|
||||
}(counter)
|
||||
}
|
||||
p.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P4runtime) Stop() {
|
||||
p.conn.Close()
|
||||
p.wg.Wait()
|
||||
}
|
||||
|
||||
func initConnection(endpoint string, tlscfg *tls.Config) (*grpc.ClientConn, error) {
|
||||
var creds credentials.TransportCredentials
|
||||
if tlscfg != nil {
|
||||
creds = credentials.NewTLS(tlscfg)
|
||||
} else {
|
||||
creds = insecure.NewCredentials()
|
||||
}
|
||||
return grpc.NewClient(endpoint, grpc.WithTransportCredentials(creds))
|
||||
}
|
||||
|
||||
func (p *P4runtime) getP4Info() (*p4_config.P4Info, error) {
|
||||
req := &p4.GetForwardingPipelineConfigRequest{
|
||||
DeviceId: p.DeviceID,
|
||||
ResponseType: p4.GetForwardingPipelineConfigRequest_ALL,
|
||||
}
|
||||
resp, err := p.client.GetForwardingPipelineConfig(context.Background(), req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when retrieving forwarding pipeline config: %w", err)
|
||||
}
|
||||
|
||||
config := resp.GetConfig()
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"error when retrieving config from forwarding pipeline - pipeline doesn't have a config yet: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
p4info := config.GetP4Info()
|
||||
if p4info == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"error when retrieving P4Info from config - config doesn't have a P4Info: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return p4info, nil
|
||||
}
|
||||
|
||||
func filterCounters(counters []*p4_config.Counter, counterNamesInclude []string) []*p4_config.Counter {
|
||||
if len(counterNamesInclude) == 0 {
|
||||
return counters
|
||||
}
|
||||
|
||||
var filteredCounters []*p4_config.Counter
|
||||
for _, counter := range counters {
|
||||
if counter == nil {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(counterNamesInclude, counter.Preamble.Name) {
|
||||
filteredCounters = append(filteredCounters, counter)
|
||||
}
|
||||
}
|
||||
return filteredCounters
|
||||
}
|
||||
|
||||
func (p *P4runtime) newP4RuntimeClient() error {
|
||||
var tlscfg *tls.Config
|
||||
var err error
|
||||
|
||||
if p.EnableTLS {
|
||||
if tlscfg, err = p.ClientConfig.TLSConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := initConnection(p.Endpoint, tlscfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot connect to the server: %w", err)
|
||||
}
|
||||
p.conn = conn
|
||||
p.client = p4.NewP4RuntimeClient(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P4runtime) readAllEntries(counterID uint32) ([]*p4.Entity, error) {
|
||||
readRequest := &p4.ReadRequest{
|
||||
DeviceId: p.DeviceID,
|
||||
Entities: []*p4.Entity{{
|
||||
Entity: &p4.Entity_CounterEntry{
|
||||
CounterEntry: &p4.CounterEntry{
|
||||
CounterId: counterID}}}}}
|
||||
|
||||
stream, err := p.client.Read(context.Background(), readRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rep, err := stream.Recv()
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rep.Entities, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("p4runtime", func() telegraf.Input {
|
||||
p4runtime := &P4runtime{
|
||||
DeviceID: defaultDeviceID,
|
||||
}
|
||||
return p4runtime
|
||||
})
|
||||
}
|
122
plugins/inputs/p4runtime/p4runtime_fake_client_test.go
Normal file
122
plugins/inputs/p4runtime/p4runtime_fake_client_test.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package p4runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
p4 "github.com/p4lang/p4runtime/go/p4/v1"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type fakeP4RuntimeClient struct {
|
||||
writeFn func(
|
||||
ctx context.Context,
|
||||
in *p4.WriteRequest,
|
||||
opts ...grpc.CallOption,
|
||||
) (*p4.WriteResponse, error)
|
||||
|
||||
readFn func(
|
||||
in *p4.ReadRequest,
|
||||
) (p4.P4Runtime_ReadClient, error)
|
||||
|
||||
setForwardingPipelineConfigFn func(
|
||||
ctx context.Context,
|
||||
in *p4.SetForwardingPipelineConfigRequest,
|
||||
opts ...grpc.CallOption,
|
||||
) (*p4.SetForwardingPipelineConfigResponse, error)
|
||||
|
||||
getForwardingPipelineConfigFn func() (*p4.GetForwardingPipelineConfigResponse, error)
|
||||
|
||||
streamChannelFn func(
|
||||
ctx context.Context,
|
||||
opts ...grpc.CallOption,
|
||||
) (p4.P4Runtime_StreamChannelClient, error)
|
||||
|
||||
capabilitiesFn func(
|
||||
ctx context.Context,
|
||||
in *p4.CapabilitiesRequest,
|
||||
opts ...grpc.CallOption,
|
||||
) (*p4.CapabilitiesResponse, error)
|
||||
}
|
||||
|
||||
// fakeP4RuntimeClient implements the v1.P4RuntimeClient interface
|
||||
var _ p4.P4RuntimeClient = &fakeP4RuntimeClient{}
|
||||
|
||||
func (c *fakeP4RuntimeClient) Write(
|
||||
ctx context.Context,
|
||||
in *p4.WriteRequest,
|
||||
opts ...grpc.CallOption,
|
||||
) (*p4.WriteResponse, error) {
|
||||
if c.writeFn == nil {
|
||||
panic("No mock defined for Write RPC")
|
||||
}
|
||||
return c.writeFn(ctx, in, opts...)
|
||||
}
|
||||
|
||||
func (c *fakeP4RuntimeClient) Read(
|
||||
_ context.Context,
|
||||
in *p4.ReadRequest,
|
||||
_ ...grpc.CallOption,
|
||||
) (p4.P4Runtime_ReadClient, error) {
|
||||
if c.readFn == nil {
|
||||
panic("No mock defined for Read RPC")
|
||||
}
|
||||
return c.readFn(in)
|
||||
}
|
||||
|
||||
func (c *fakeP4RuntimeClient) SetForwardingPipelineConfig(
|
||||
ctx context.Context,
|
||||
in *p4.SetForwardingPipelineConfigRequest,
|
||||
opts ...grpc.CallOption,
|
||||
) (*p4.SetForwardingPipelineConfigResponse, error) {
|
||||
if c.setForwardingPipelineConfigFn == nil {
|
||||
panic("No mock defined for SetForwardingPipelineConfig RPC")
|
||||
}
|
||||
return c.setForwardingPipelineConfigFn(ctx, in, opts...)
|
||||
}
|
||||
|
||||
func (c *fakeP4RuntimeClient) GetForwardingPipelineConfig(
|
||||
context.Context,
|
||||
*p4.GetForwardingPipelineConfigRequest,
|
||||
...grpc.CallOption,
|
||||
) (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
if c.getForwardingPipelineConfigFn == nil {
|
||||
panic("No mock defined for GetForwardingPipelineConfig RPC")
|
||||
}
|
||||
return c.getForwardingPipelineConfigFn()
|
||||
}
|
||||
|
||||
func (c *fakeP4RuntimeClient) StreamChannel(
|
||||
ctx context.Context,
|
||||
opts ...grpc.CallOption,
|
||||
) (p4.P4Runtime_StreamChannelClient, error) {
|
||||
if c.streamChannelFn == nil {
|
||||
panic("No mock defined for StreamChannel")
|
||||
}
|
||||
return c.streamChannelFn(ctx, opts...)
|
||||
}
|
||||
|
||||
func (c *fakeP4RuntimeClient) Capabilities(
|
||||
ctx context.Context,
|
||||
in *p4.CapabilitiesRequest,
|
||||
opts ...grpc.CallOption,
|
||||
) (*p4.CapabilitiesResponse, error) {
|
||||
if c.capabilitiesFn == nil {
|
||||
panic("No mock defined for Capabilities RPC")
|
||||
}
|
||||
return c.capabilitiesFn(ctx, in, opts...)
|
||||
}
|
||||
|
||||
type fakeP4RuntimeReadClient struct {
|
||||
grpc.ClientStream
|
||||
recvFn func() (*p4.ReadResponse, error)
|
||||
}
|
||||
|
||||
// fakeP4RuntimeReadClient implements the v1.P4Runtime_ReadClient interface
|
||||
var _ p4.P4Runtime_ReadClient = &fakeP4RuntimeReadClient{}
|
||||
|
||||
func (c *fakeP4RuntimeReadClient) Recv() (*p4.ReadResponse, error) {
|
||||
if c.recvFn == nil {
|
||||
panic("No mock provided for Recv function")
|
||||
}
|
||||
return c.recvFn()
|
||||
}
|
614
plugins/inputs/p4runtime/p4runtime_test.go
Normal file
614
plugins/inputs/p4runtime/p4runtime_test.go
Normal file
|
@ -0,0 +1,614 @@
|
|||
package p4runtime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
p4_config "github.com/p4lang/p4runtime/go/p4/config/v1"
|
||||
p4 "github.com/p4lang/p4runtime/go/p4/v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
// CounterSpec available here https://github.com/p4lang/p4runtime/blob/main/proto/p4/config/v1/p4info.proto#L289
|
||||
func createCounter(
|
||||
name string,
|
||||
id uint32,
|
||||
unit p4_config.CounterSpec_Unit,
|
||||
) *p4_config.Counter {
|
||||
return &p4_config.Counter{
|
||||
Preamble: &p4_config.Preamble{Name: name, Id: id},
|
||||
Spec: &p4_config.CounterSpec{Unit: unit},
|
||||
}
|
||||
}
|
||||
|
||||
func createEntityCounterEntry(
|
||||
counterID uint32,
|
||||
index int64,
|
||||
data *p4.CounterData,
|
||||
) *p4.Entity_CounterEntry {
|
||||
return &p4.Entity_CounterEntry{
|
||||
CounterEntry: &p4.CounterEntry{
|
||||
CounterId: counterID,
|
||||
Index: &p4.Index{Index: index},
|
||||
Data: data,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTestP4RuntimeClient(
|
||||
p4RuntimeClient *fakeP4RuntimeClient,
|
||||
addr string,
|
||||
t *testing.T,
|
||||
) *P4runtime {
|
||||
conn, err := grpc.NewClient(
|
||||
addr,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return &P4runtime{
|
||||
Endpoint: addr,
|
||||
DeviceID: uint64(1),
|
||||
Log: testutil.Logger{},
|
||||
conn: conn,
|
||||
client: p4RuntimeClient,
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitDefault(t *testing.T) {
|
||||
plugin := &P4runtime{Log: testutil.Logger{}}
|
||||
require.NoError(t, plugin.Init())
|
||||
require.Equal(t, "127.0.0.1:9559", plugin.Endpoint)
|
||||
require.Equal(t, uint64(0), plugin.DeviceID)
|
||||
require.Empty(t, plugin.CounterNamesInclude)
|
||||
require.False(t, plugin.EnableTLS)
|
||||
}
|
||||
|
||||
func TestErrorGetP4Info(t *testing.T) {
|
||||
responses := []struct {
|
||||
getForwardingPipelineConfigResponse *p4.GetForwardingPipelineConfigResponse
|
||||
getForwardingPipelineConfigResponseError error
|
||||
}{
|
||||
{
|
||||
getForwardingPipelineConfigResponse: nil,
|
||||
getForwardingPipelineConfigResponseError: errors.New("error when retrieving forwarding pipeline config"),
|
||||
}, {
|
||||
getForwardingPipelineConfigResponse: &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: nil,
|
||||
},
|
||||
getForwardingPipelineConfigResponseError: nil,
|
||||
}, {
|
||||
getForwardingPipelineConfigResponse: &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: &p4.ForwardingPipelineConfig{P4Info: nil},
|
||||
},
|
||||
getForwardingPipelineConfigResponseError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, response := range responses {
|
||||
p4RtClient := &fakeP4RuntimeClient{
|
||||
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
return response.getForwardingPipelineConfigResponse, response.getForwardingPipelineConfigResponseError
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.Error(t, plugin.Gather(&acc))
|
||||
}
|
||||
}
|
||||
|
||||
func TestOneCounterRead(t *testing.T) {
|
||||
tests := []struct {
|
||||
forwardingPipelineConfig *p4.ForwardingPipelineConfig
|
||||
EntityCounterEntry *p4.Entity_CounterEntry
|
||||
expected []telegraf.Metric
|
||||
}{
|
||||
{
|
||||
forwardingPipelineConfig: &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: []*p4_config.Counter{
|
||||
createCounter("foo", 1111, p4_config.CounterSpec_BOTH),
|
||||
},
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
},
|
||||
EntityCounterEntry: createEntityCounterEntry(
|
||||
1111,
|
||||
5,
|
||||
&p4.CounterData{ByteCount: 5, PacketCount: 1},
|
||||
),
|
||||
expected: []telegraf.Metric{testutil.MustMetric(
|
||||
"p4_runtime",
|
||||
map[string]string{
|
||||
"p4program_name": "P4Program",
|
||||
"counter_name": "foo",
|
||||
"counter_type": "BOTH",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bytes": int64(5),
|
||||
"packets": int64(1),
|
||||
"counter_index": 5},
|
||||
time.Unix(0, 0)),
|
||||
},
|
||||
}, {
|
||||
forwardingPipelineConfig: &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: []*p4_config.Counter{
|
||||
createCounter(
|
||||
"foo",
|
||||
2222,
|
||||
p4_config.CounterSpec_BYTES,
|
||||
),
|
||||
},
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
},
|
||||
EntityCounterEntry: createEntityCounterEntry(
|
||||
2222,
|
||||
5,
|
||||
&p4.CounterData{ByteCount: 5},
|
||||
),
|
||||
expected: []telegraf.Metric{testutil.MustMetric(
|
||||
"p4_runtime",
|
||||
map[string]string{
|
||||
"p4program_name": "P4Program",
|
||||
"counter_name": "foo",
|
||||
"counter_type": "BYTES",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bytes": int64(5),
|
||||
"packets": int64(0),
|
||||
"counter_index": 5},
|
||||
time.Unix(0, 0)),
|
||||
},
|
||||
}, {
|
||||
forwardingPipelineConfig: &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: []*p4_config.Counter{
|
||||
createCounter(
|
||||
"foo",
|
||||
3333,
|
||||
p4_config.CounterSpec_PACKETS,
|
||||
),
|
||||
},
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
},
|
||||
EntityCounterEntry: createEntityCounterEntry(
|
||||
3333,
|
||||
5,
|
||||
&p4.CounterData{PacketCount: 1},
|
||||
),
|
||||
expected: []telegraf.Metric{testutil.MustMetric(
|
||||
"p4_runtime",
|
||||
map[string]string{
|
||||
"p4program_name": "P4Program",
|
||||
"counter_name": "foo",
|
||||
"counter_type": "PACKETS",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bytes": int64(0),
|
||||
"packets": int64(1),
|
||||
"counter_index": 5},
|
||||
time.Unix(0, 0)),
|
||||
},
|
||||
}, {
|
||||
forwardingPipelineConfig: &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: []*p4_config.Counter{
|
||||
createCounter("foo", 4444, p4_config.CounterSpec_BOTH),
|
||||
},
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
},
|
||||
EntityCounterEntry: createEntityCounterEntry(
|
||||
4444,
|
||||
5,
|
||||
&p4.CounterData{},
|
||||
),
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
p4RtReadClient := &fakeP4RuntimeReadClient{
|
||||
recvFn: func() (*p4.ReadResponse, error) {
|
||||
return &p4.ReadResponse{
|
||||
Entities: []*p4.Entity{{Entity: tt.EntityCounterEntry}},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
p4RtClient := &fakeP4RuntimeClient{
|
||||
readFn: func(*p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
|
||||
return p4RtReadClient, nil
|
||||
},
|
||||
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
return &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: tt.forwardingPipelineConfig,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
|
||||
testutil.RequireMetricsEqual(
|
||||
t,
|
||||
tt.expected,
|
||||
acc.GetTelegrafMetrics(),
|
||||
testutil.IgnoreTime(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleEntitiesSingleCounterRead(t *testing.T) {
|
||||
totalNumOfEntriesArr := [3]int{2, 10, 100}
|
||||
|
||||
for _, totalNumOfEntries := range totalNumOfEntriesArr {
|
||||
var expected []telegraf.Metric
|
||||
|
||||
fmt.Println(
|
||||
"Running TestMultipleEntitiesSingleCounterRead with ",
|
||||
totalNumOfEntries,
|
||||
"totalNumOfCounters",
|
||||
)
|
||||
entities := make([]*p4.Entity, 0, totalNumOfEntries)
|
||||
p4InfoCounters := make([]*p4_config.Counter, 0, totalNumOfEntries)
|
||||
p4InfoCounters = append(
|
||||
p4InfoCounters,
|
||||
createCounter("foo", 0, p4_config.CounterSpec_BOTH),
|
||||
)
|
||||
|
||||
for i := 0; i < totalNumOfEntries; i++ {
|
||||
counterEntry := &p4.Entity{
|
||||
Entity: createEntityCounterEntry(
|
||||
0,
|
||||
int64(i),
|
||||
&p4.CounterData{
|
||||
ByteCount: int64(10),
|
||||
PacketCount: int64(10),
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
entities = append(entities, counterEntry)
|
||||
expected = append(expected, testutil.MustMetric(
|
||||
"p4_runtime",
|
||||
map[string]string{
|
||||
"p4program_name": "P4Program",
|
||||
"counter_name": "foo",
|
||||
"counter_type": "BOTH",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bytes": int64(10),
|
||||
"packets": int64(10),
|
||||
"counter_index": i,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
))
|
||||
}
|
||||
|
||||
forwardingPipelineConfig := &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: p4InfoCounters,
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
}
|
||||
|
||||
p4RtReadClient := &fakeP4RuntimeReadClient{
|
||||
recvFn: func() (*p4.ReadResponse, error) {
|
||||
return &p4.ReadResponse{Entities: entities}, nil
|
||||
},
|
||||
}
|
||||
|
||||
p4RtClient := &fakeP4RuntimeClient{
|
||||
readFn: func(*p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
|
||||
return p4RtReadClient, nil
|
||||
},
|
||||
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
return &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: forwardingPipelineConfig,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
acc.Wait(totalNumOfEntries)
|
||||
|
||||
testutil.RequireMetricsEqual(
|
||||
t,
|
||||
expected,
|
||||
acc.GetTelegrafMetrics(),
|
||||
testutil.IgnoreTime(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingleEntitiesMultipleCounterRead(t *testing.T) {
|
||||
totalNumOfCountersArr := [3]int{2, 10, 100}
|
||||
|
||||
for _, totalNumOfCounters := range totalNumOfCountersArr {
|
||||
var expected []telegraf.Metric
|
||||
|
||||
fmt.Println(
|
||||
"Running TestSingleEntitiesMultipleCounterRead with ",
|
||||
totalNumOfCounters,
|
||||
"totalNumOfCounters",
|
||||
)
|
||||
p4InfoCounters := make([]*p4_config.Counter, 0, totalNumOfCounters)
|
||||
|
||||
for i := 1; i <= totalNumOfCounters; i++ {
|
||||
counterName := fmt.Sprintf("foo%v", i)
|
||||
p4InfoCounters = append(
|
||||
p4InfoCounters,
|
||||
createCounter(
|
||||
counterName,
|
||||
uint32(i),
|
||||
p4_config.CounterSpec_BOTH,
|
||||
),
|
||||
)
|
||||
|
||||
expected = append(expected, testutil.MustMetric(
|
||||
"p4_runtime",
|
||||
map[string]string{
|
||||
"p4program_name": "P4Program",
|
||||
"counter_name": counterName,
|
||||
"counter_type": "BOTH",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bytes": int64(10),
|
||||
"packets": int64(10),
|
||||
"counter_index": 1,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
))
|
||||
}
|
||||
|
||||
forwardingPipelineConfig := &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: p4InfoCounters,
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
}
|
||||
|
||||
p4RtClient := &fakeP4RuntimeClient{
|
||||
readFn: func(in *p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
|
||||
counterID := in.Entities[0].GetCounterEntry().CounterId
|
||||
return &fakeP4RuntimeReadClient{
|
||||
recvFn: func() (*p4.ReadResponse, error) {
|
||||
return &p4.ReadResponse{
|
||||
Entities: []*p4.Entity{{
|
||||
Entity: createEntityCounterEntry(
|
||||
counterID,
|
||||
1,
|
||||
&p4.CounterData{
|
||||
ByteCount: 10,
|
||||
PacketCount: 10,
|
||||
},
|
||||
),
|
||||
}},
|
||||
}, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
return &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: forwardingPipelineConfig,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
acc.Wait(totalNumOfCounters)
|
||||
|
||||
testutil.RequireMetricsEqual(
|
||||
t,
|
||||
expected,
|
||||
acc.GetTelegrafMetrics(),
|
||||
testutil.SortMetrics(),
|
||||
testutil.IgnoreTime(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoCountersAvailable(t *testing.T) {
|
||||
forwardingPipelineConfig := &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{Counters: make([]*p4_config.Counter, 0)},
|
||||
}
|
||||
|
||||
p4RtClient := &fakeP4RuntimeClient{
|
||||
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
return &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: forwardingPipelineConfig,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
}
|
||||
|
||||
func TestFilterCounters(t *testing.T) {
|
||||
forwardingPipelineConfig := &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: []*p4_config.Counter{
|
||||
createCounter("foo", 1, p4_config.CounterSpec_BOTH),
|
||||
},
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
}
|
||||
|
||||
p4RtClient := &fakeP4RuntimeClient{
|
||||
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
return &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: forwardingPipelineConfig,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
|
||||
|
||||
plugin.CounterNamesInclude = []string{"oof"}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
testutil.RequireMetricsEqual(
|
||||
t,
|
||||
nil,
|
||||
acc.GetTelegrafMetrics(),
|
||||
testutil.IgnoreTime(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestFailReadCounterEntryFromEntry(t *testing.T) {
|
||||
p4RtReadClient := &fakeP4RuntimeReadClient{
|
||||
recvFn: func() (*p4.ReadResponse, error) {
|
||||
return &p4.ReadResponse{
|
||||
Entities: []*p4.Entity{{
|
||||
Entity: &p4.Entity_TableEntry{
|
||||
TableEntry: &p4.TableEntry{},
|
||||
}}}}, nil
|
||||
},
|
||||
}
|
||||
|
||||
p4RtClient := &fakeP4RuntimeClient{
|
||||
readFn: func(*p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
|
||||
return p4RtReadClient, nil
|
||||
},
|
||||
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
return &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: []*p4_config.Counter{
|
||||
createCounter(
|
||||
"foo",
|
||||
1111,
|
||||
p4_config.CounterSpec_BOTH,
|
||||
),
|
||||
},
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
require.Equal(
|
||||
t,
|
||||
errors.New("reading counter entry from entry table_entry:{} failed"),
|
||||
acc.Errors[0],
|
||||
)
|
||||
testutil.RequireMetricsEqual(
|
||||
t,
|
||||
nil,
|
||||
acc.GetTelegrafMetrics(),
|
||||
testutil.IgnoreTime(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestFailReadAllEntries(t *testing.T) {
|
||||
p4RtClient := &fakeP4RuntimeClient{
|
||||
readFn: func(*p4.ReadRequest) (p4.P4Runtime_ReadClient, error) {
|
||||
return nil, errors.New("connection error")
|
||||
},
|
||||
getForwardingPipelineConfigFn: func() (*p4.GetForwardingPipelineConfigResponse, error) {
|
||||
return &p4.GetForwardingPipelineConfigResponse{
|
||||
Config: &p4.ForwardingPipelineConfig{
|
||||
P4Info: &p4_config.P4Info{
|
||||
Counters: []*p4_config.Counter{
|
||||
createCounter(
|
||||
"foo",
|
||||
1111,
|
||||
p4_config.CounterSpec_BOTH,
|
||||
),
|
||||
},
|
||||
PkgInfo: &p4_config.PkgInfo{Name: "P4Program"},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
plugin := newTestP4RuntimeClient(p4RtClient, listener.Addr().String(), t)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
require.Equal(
|
||||
t,
|
||||
acc.Errors[0],
|
||||
fmt.Errorf("reading counter entries with ID=1111 failed with error: %w", errors.New("connection error")),
|
||||
)
|
||||
testutil.RequireMetricsEqual(
|
||||
t,
|
||||
nil,
|
||||
acc.GetTelegrafMetrics(),
|
||||
testutil.IgnoreTime(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestFilterCounterNamesInclude(t *testing.T) {
|
||||
counters := []*p4_config.Counter{
|
||||
createCounter("foo", 1, p4_config.CounterSpec_BOTH),
|
||||
createCounter("bar", 2, p4_config.CounterSpec_BOTH),
|
||||
nil,
|
||||
createCounter("", 3, p4_config.CounterSpec_BOTH),
|
||||
}
|
||||
|
||||
counterNamesInclude := []string{"bar"}
|
||||
|
||||
filteredCounters := filterCounters(counters, counterNamesInclude)
|
||||
require.Equal(
|
||||
t,
|
||||
[]*p4_config.Counter{
|
||||
createCounter("bar", 2, p4_config.CounterSpec_BOTH),
|
||||
}, filteredCounters,
|
||||
)
|
||||
}
|
23
plugins/inputs/p4runtime/sample.conf
Normal file
23
plugins/inputs/p4runtime/sample.conf
Normal file
|
@ -0,0 +1,23 @@
|
|||
# P4Runtime telemetry input plugin
|
||||
[[inputs.p4runtime]]
|
||||
## Define the endpoint of P4Runtime gRPC server to collect metrics.
|
||||
# endpoint = "127.0.0.1:9559"
|
||||
## Set DeviceID required for Client Arbitration.
|
||||
## https://p4.org/p4-spec/p4runtime/main/P4Runtime-Spec.html#sec-client-arbitration-and-controller-replication
|
||||
# device_id = 1
|
||||
## Filter counters by their names that should be observed.
|
||||
## Example: counter_names_include=["ingressCounter", "egressCounter"]
|
||||
# counter_names_include = []
|
||||
|
||||
## Optional TLS Config.
|
||||
## Enable client-side TLS and define CA to authenticate the device.
|
||||
# enable_tls = false
|
||||
# tls_ca = "/etc/telegraf/ca.crt"
|
||||
## Set minimal TLS version to accept by the client.
|
||||
# tls_min_version = "TLS12"
|
||||
## Use TLS but skip chain & host verification.
|
||||
# insecure_skip_verify = true
|
||||
|
||||
## Define client-side TLS certificate & key to authenticate to the device.
|
||||
# tls_cert = "/etc/telegraf/client.crt"
|
||||
# tls_key = "/etc/telegraf/client.key"
|
Loading…
Add table
Add a link
Reference in a new issue