1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,105 @@
# Juniper Telemetry Input Plugin
This service plugin reads [OpenConfig][openconfig] telemetry data via the
[Junos Telemetry Interface (JTI)][jti] from configured from listed sensors.
⭐ Telegraf v1.7.0
🏷️ network, iot
💻 all
[openconfig]: http://openconfig.net/
[jti]: https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-telemetry-interface-oveview.html
## Service Input <!-- @/docs/includes/service_input.md -->
This plugin is a service input. Normal plugins gather metrics determined by the
interval setting. Service plugins start a service to listen and wait for
metrics or events to occur. Service plugins have two key differences from
normal plugins:
1. The global or plugin specific `interval` setting may not apply
2. The CLI options of `--test`, `--test-wait`, and `--once` may not produce
output for this plugin
## 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
# Subscribe and receive OpenConfig Telemetry data using JTI
[[inputs.jti_openconfig_telemetry]]
## List of device addresses to collect telemetry from
servers = ["localhost:1883"]
## Authentication details. Username and password are must if device expects
## authentication. Client ID must be unique when connecting from multiple instances
## of telegraf to the same device
username = "user"
password = "pass"
client_id = "telegraf"
## Frequency to get data
sample_frequency = "1000ms"
## Sensors to subscribe for
## A identifier for each sensor can be provided in path by separating with space
## Else sensor path will be used as identifier
## When identifier is used, we can provide a list of space separated sensors.
## A single subscription will be created with all these sensors and data will
## be saved to measurement with this identifier name
sensors = [
"/interfaces/",
"collection /components/ /lldp",
]
## We allow specifying sensor group level reporting rate. To do this, specify the
## reporting rate in Duration at the beginning of sensor paths / collection
## name. For entries without reporting rate, we use configured sample frequency
sensors = [
"1000ms customReporting /interfaces /lldp",
"2000ms collection /components",
"/interfaces",
]
## Timestamp Source
## Set to 'collection' for time of collection, and 'data' for using the time
## provided by the _timestamp field.
# timestamp_source = "collection"
## Optional TLS Config
# enable_tls = false
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Minimal TLS version to accept by the client
# tls_min_version = "TLS12"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
## Delay between retry attempts of failed RPC calls or streams. Defaults to 1000ms.
## Failed streams/calls will not be retried if 0 is provided
retry_delay = "1000ms"
## Period for sending keep-alive packets on idle connections
## This is helpful to identify broken connections to the server
# keep_alive_period = "10s"
## To treat all string values as tags, set this to true
str_as_tags = false
```
## Tags
- All measurements are tagged appropriately using the identifier information
in incoming data
## Example Output
## Metrics

View file

@ -0,0 +1,238 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// source: auth/authentication_service.proto
package authentication
import (
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// The request message containing the user's name, password and client id
type LoginRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
ClientId string `protobuf:"bytes,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
}
func (x *LoginRequest) Reset() {
*x = LoginRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_auth_authentication_service_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LoginRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LoginRequest) ProtoMessage() {}
func (x *LoginRequest) ProtoReflect() protoreflect.Message {
mi := &file_auth_authentication_service_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead.
func (*LoginRequest) Descriptor() ([]byte, []int) {
return file_auth_authentication_service_proto_rawDescGZIP(), []int{0}
}
func (x *LoginRequest) GetUserName() string {
if x != nil {
return x.UserName
}
return ""
}
func (x *LoginRequest) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
func (x *LoginRequest) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
// The response message containing the result of login attempt.
// result value of true indicates success and false indicates
// failure
type LoginReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Result bool `protobuf:"varint,1,opt,name=result,proto3" json:"result,omitempty"`
}
func (x *LoginReply) Reset() {
*x = LoginReply{}
if protoimpl.UnsafeEnabled {
mi := &file_auth_authentication_service_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LoginReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LoginReply) ProtoMessage() {}
func (x *LoginReply) ProtoReflect() protoreflect.Message {
mi := &file_auth_authentication_service_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LoginReply.ProtoReflect.Descriptor instead.
func (*LoginReply) Descriptor() ([]byte, []int) {
return file_auth_authentication_service_proto_rawDescGZIP(), []int{1}
}
func (x *LoginReply) GetResult() bool {
if x != nil {
return x.Result
}
return false
}
var File_auth_authentication_service_proto protoreflect.FileDescriptor
var file_auth_authentication_service_proto_rawDesc = []byte{
0x0a, 0x21, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x22, 0x64, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65,
0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x24, 0x0a, 0x0a, 0x4c, 0x6f, 0x67,
0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32,
0x51, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x48, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69,
0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1c, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x00, 0x42, 0x12, 0x5a, 0x10, 0x2e, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_auth_authentication_service_proto_rawDescOnce sync.Once
file_auth_authentication_service_proto_rawDescData = file_auth_authentication_service_proto_rawDesc
)
func file_auth_authentication_service_proto_rawDescGZIP() []byte {
file_auth_authentication_service_proto_rawDescOnce.Do(func() {
file_auth_authentication_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_auth_authentication_service_proto_rawDescData)
})
return file_auth_authentication_service_proto_rawDescData
}
var file_auth_authentication_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_auth_authentication_service_proto_goTypes = []interface{}{
(*LoginRequest)(nil), // 0: authentication.LoginRequest
(*LoginReply)(nil), // 1: authentication.LoginReply
}
var file_auth_authentication_service_proto_depIdxs = []int32{
0, // 0: authentication.Login.LoginCheck:input_type -> authentication.LoginRequest
1, // 1: authentication.Login.LoginCheck:output_type -> authentication.LoginReply
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_auth_authentication_service_proto_init() }
func file_auth_authentication_service_proto_init() {
if File_auth_authentication_service_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_auth_authentication_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LoginRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_auth_authentication_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LoginReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_auth_authentication_service_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_auth_authentication_service_proto_goTypes,
DependencyIndexes: file_auth_authentication_service_proto_depIdxs,
MessageInfos: file_auth_authentication_service_proto_msgTypes,
}.Build()
File_auth_authentication_service_proto = out.File
file_auth_authentication_service_proto_rawDesc = nil
file_auth_authentication_service_proto_goTypes = nil
file_auth_authentication_service_proto_depIdxs = nil
}

View file

@ -0,0 +1,49 @@
//
// Copyrights (c) 2017, Juniper Networks, Inc.
// All rights reserved.
//
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
syntax = "proto3";
package authentication;
option go_package = ".;authentication";
// The Login service definition.
service Login {
rpc LoginCheck (LoginRequest) returns (LoginReply) {}
}
// The request message containing the user's name, password and client id
message LoginRequest {
string user_name = 1;
string password = 2;
string client_id = 3;
}
/*
* The response message containing the result of login attempt.
* result value of true indicates success and false indicates
* failure
*/
message LoginReply {
bool result = 1;
}

View file

@ -0,0 +1,102 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package authentication
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// LoginClient is the client API for Login service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type LoginClient interface {
LoginCheck(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginReply, error)
}
type loginClient struct {
cc grpc.ClientConnInterface
}
func NewLoginClient(cc grpc.ClientConnInterface) LoginClient {
return &loginClient{cc}
}
func (c *loginClient) LoginCheck(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginReply, error) {
out := new(LoginReply)
err := c.cc.Invoke(ctx, "/authentication.Login/LoginCheck", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// LoginServer is the server API for Login service.
// All implementations must embed UnimplementedLoginServer
// for forward compatibility
type LoginServer interface {
LoginCheck(context.Context, *LoginRequest) (*LoginReply, error)
mustEmbedUnimplementedLoginServer()
}
// UnimplementedLoginServer must be embedded to have forward compatible implementations.
type UnimplementedLoginServer struct {
}
func (UnimplementedLoginServer) LoginCheck(context.Context, *LoginRequest) (*LoginReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method LoginCheck not implemented")
}
func (UnimplementedLoginServer) mustEmbedUnimplementedLoginServer() {}
// UnsafeLoginServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to LoginServer will
// result in compilation errors.
type UnsafeLoginServer interface {
mustEmbedUnimplementedLoginServer()
}
func RegisterLoginServer(s grpc.ServiceRegistrar, srv LoginServer) {
s.RegisterService(&Login_ServiceDesc, srv)
}
func _Login_LoginCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoginRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LoginServer).LoginCheck(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/authentication.Login/LoginCheck",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LoginServer).LoginCheck(ctx, req.(*LoginRequest))
}
return interceptor(ctx, in, info, handler)
}
// Login_ServiceDesc is the grpc.ServiceDesc for Login service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Login_ServiceDesc = grpc.ServiceDesc{
ServiceName: "authentication.Login",
HandlerType: (*LoginServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "LoginCheck",
Handler: _Login_LoginCheck_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "auth/authentication_service.proto",
}

View file

@ -0,0 +1,59 @@
package jti_openconfig_telemetry
import "sort"
type dataGroup struct {
numKeys int
tags map[string]string
data map[string]interface{}
}
// Sort the data groups by number of keys
type collectionByKeys []dataGroup
func (a collectionByKeys) Len() int { return len(a) }
func (a collectionByKeys) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a collectionByKeys) Less(i, j int) bool { return a[i].numKeys < a[j].numKeys }
// Checks to see if there is already a group with these tags and returns its index. Returns -1 if unavailable.
func (a collectionByKeys) isAvailable(tags map[string]string) *dataGroup {
sort.Sort(a)
// Iterate through all the groups and see if we have group with these tags
for _, group := range a {
// Since already sorted, match with only groups with N keys
if group.numKeys < len(tags) {
continue
} else if group.numKeys > len(tags) {
break
}
matchFound := true
for k, v := range tags {
val, ok := group.tags[k]
if !ok || val != v {
matchFound = false
break
}
}
if matchFound {
return &group
}
}
return nil
}
// Inserts into already existing group or creates a new group
func (a collectionByKeys) insert(tags map[string]string, data map[string]interface{}) collectionByKeys {
// If there is already a group with this set of tags, insert into it. Otherwise create a new group and insert
if group := a.isAvailable(tags); group != nil {
for k, v := range data {
group.data[k] = v
}
} else {
a = append(a, dataGroup{len(tags), tags, data})
}
return a
}

View file

@ -0,0 +1,11 @@
package jti_openconfig_telemetry
// To run these commands, make sure that protoc-gen-go and protoc-gen-go-grpc are installed
// > go install google.golang.org/protobuf/cmd/protoc-gen-go
// > go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
//
// Generated files were last generated with:
// - protoc-gen-go: v1.27.1
// - protoc-gen-go-grpc: v1.1.0
//go:generate protoc --go_out=auth/ --go-grpc_out=auth/ auth/authentication_service.proto
//go:generate protoc --go_out=oc/ --go-grpc_out=oc/ oc/oc.proto

View file

@ -0,0 +1,461 @@
//go:generate ../../../tools/readme_config_includer/generator
package jti_openconfig_telemetry
import (
"context"
_ "embed"
"errors"
"fmt"
"net"
"regexp"
"strings"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
common_tls "github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/inputs"
authentication "github.com/influxdata/telegraf/plugins/inputs/jti_openconfig_telemetry/auth"
telemetry "github.com/influxdata/telegraf/plugins/inputs/jti_openconfig_telemetry/oc"
)
//go:embed sample.conf
var sampleConfig string
var (
// Regex to match and extract data points from path value in received key
keyPathRegex = regexp.MustCompile(`/([^/]*)\[([A-Za-z0-9\-/]*=[^\[]*)]`)
)
type OpenConfigTelemetry struct {
Servers []string `toml:"servers"`
Sensors []string `toml:"sensors"`
Username string `toml:"username"`
Password string `toml:"password"`
ClientID string `toml:"client_id"`
TimestampSource string `toml:"timestamp_source"`
SampleFrequency config.Duration `toml:"sample_frequency"`
StrAsTags bool `toml:"str_as_tags"`
RetryDelay config.Duration `toml:"retry_delay"`
EnableTLS bool `toml:"enable_tls"`
KeepAlivePeriod config.Duration `toml:"keep_alive_period"`
common_tls.ClientConfig
Log telegraf.Logger `toml:"-"`
sensorsConfig []sensorConfig
grpcClientConns []grpcConnection
wg *sync.WaitGroup
}
// Structure to hold sensors path list and measurement name
type sensorConfig struct {
measurementName string
pathList []*telemetry.Path
}
type grpcConnection struct {
connection *grpc.ClientConn
cancel context.CancelFunc
}
func (g *grpcConnection) close() {
g.connection.Close()
g.cancel()
}
func (*OpenConfigTelemetry) SampleConfig() string {
return sampleConfig
}
func (m *OpenConfigTelemetry) Init() error {
switch m.TimestampSource {
case "", "collection":
case "data":
default:
return fmt.Errorf("unknown option for timestamp_source: %q", m.TimestampSource)
}
return nil
}
func (m *OpenConfigTelemetry) Start(acc telegraf.Accumulator) error {
// Build sensors config
if m.splitSensorConfig() == 0 {
return errors.New("no valid sensor configuration available")
}
// Parse TLS config
var creds credentials.TransportCredentials
if m.EnableTLS {
tlscfg, err := m.ClientConfig.TLSConfig()
if err != nil {
return err
}
creds = credentials.NewTLS(tlscfg)
} else {
creds = insecure.NewCredentials()
}
// Setup the basic connection options
options := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
}
// Add keep-alive settings
if m.KeepAlivePeriod > 0 {
params := keepalive.ClientParameters{
Time: time.Duration(m.KeepAlivePeriod),
Timeout: 2 * time.Duration(m.KeepAlivePeriod),
}
options = append(options, grpc.WithKeepaliveParams(params))
}
// Connect to given list of servers and start collecting data
var grpcClientConn *grpc.ClientConn
var wg sync.WaitGroup
m.wg = &wg
for _, server := range m.Servers {
ctx, cancel := context.WithCancel(context.Background())
if len(m.Username) > 0 {
ctx = metadata.AppendToOutgoingContext(
ctx,
"username", m.Username,
"password", m.Password,
"clientid", m.ClientID,
)
}
// Extract device address and port
grpcServer, grpcPort, err := net.SplitHostPort(server)
if err != nil {
m.Log.Errorf("Invalid server address: %s", err.Error())
cancel()
continue
}
grpcClientConn, err = grpc.NewClient(server, options...)
if err != nil {
m.Log.Errorf("Failed to connect to %s: %s", server, err.Error())
} else {
m.Log.Debugf("Opened a new gRPC session to %s on port %s", grpcServer, grpcPort)
}
// Add to the list of client connections
connection := grpcConnection{
connection: grpcClientConn,
cancel: cancel,
}
m.grpcClientConns = append(m.grpcClientConns, connection)
if m.Username != "" && m.Password != "" && m.ClientID != "" {
if err := m.authenticate(ctx, server, grpcClientConn); err != nil {
m.Log.Errorf("Error authenticating to %s: %v", grpcServer, err)
continue
}
}
// Subscribe and gather telemetry data
m.collectData(ctx, grpcServer, grpcClientConn, acc)
}
return nil
}
func (*OpenConfigTelemetry) Gather(telegraf.Accumulator) error {
return nil
}
func (m *OpenConfigTelemetry) Stop() {
for _, grpcClientConn := range m.grpcClientConns {
grpcClientConn.close()
}
m.wg.Wait()
}
// Takes in XML path with predicates and returns list of tags+values along with a final
// XML path without predicates. If /events/event[id=2]/attributes[key='message']/value
// is given input, this function will emit /events/event/attributes/value as xmlpath and
// { /events/event/@id=2, /events/event/attributes/@key='message' } as tags
func spitTagsNPath(xmlpath string) (string, map[string]string) {
subs := keyPathRegex.FindAllStringSubmatch(xmlpath, -1)
tags := make(map[string]string)
// Given XML path, this will spit out final path without predicates
if len(subs) > 0 {
for _, sub := range subs {
tagKey := strings.Split(xmlpath, sub[0])[0] + "/" + strings.TrimSpace(sub[1]) + "/@"
// If we have multiple keys in give path like /events/event[id=2 and type=3]/,
// we must emit multiple tags
for _, kv := range strings.Split(sub[2], " and ") {
key := tagKey + strings.TrimSpace(strings.Split(kv, "=")[0])
tagValue := strings.ReplaceAll(strings.Split(kv, "=")[1], "'", "")
tags[key] = tagValue
}
xmlpath = strings.Replace(xmlpath, sub[0], "/"+strings.TrimSpace(sub[1]), 1)
}
}
return xmlpath, tags
}
// Takes in a OC response, extracts tag information from keys and returns a
// list of groups with unique sets of tags+values
func (m *OpenConfigTelemetry) extractData(r *telemetry.OpenConfigData, grpcServer string) []dataGroup {
// Use empty prefix. We will update this when we iterate over key-value pairs
prefix := ""
dgroups := make([]dataGroup, 0, 5*len(r.Kv))
for _, v := range r.Kv {
kv := make(map[string]interface{})
if v.Key == "__prefix__" {
prefix = v.GetStrValue()
continue
}
// Also, lets use prefix if there is one
xmlpath, finaltags := spitTagsNPath(prefix + v.Key)
finaltags["device"] = grpcServer
switch v.Value.(type) {
case *telemetry.KeyValue_StrValue:
// If StrAsTags is set, we treat all string values as tags
if m.StrAsTags {
finaltags[xmlpath] = v.GetStrValue()
} else {
kv[xmlpath] = v.GetStrValue()
}
case *telemetry.KeyValue_DoubleValue:
kv[xmlpath] = v.GetDoubleValue()
case *telemetry.KeyValue_IntValue:
kv[xmlpath] = v.GetIntValue()
case *telemetry.KeyValue_UintValue:
kv[xmlpath] = v.GetUintValue()
case *telemetry.KeyValue_SintValue:
kv[xmlpath] = v.GetSintValue()
case *telemetry.KeyValue_BoolValue:
kv[xmlpath] = v.GetBoolValue()
case *telemetry.KeyValue_BytesValue:
kv[xmlpath] = v.GetBytesValue()
}
// Insert other tags from message
finaltags["system_id"] = r.SystemId
finaltags["path"] = r.Path
// Insert derived key and value
dgroups = collectionByKeys(dgroups).insert(finaltags, kv)
// Insert data from message header
dgroups = collectionByKeys(dgroups).insert(finaltags,
map[string]interface{}{"_sequence": r.SequenceNumber})
dgroups = collectionByKeys(dgroups).insert(finaltags,
map[string]interface{}{"_timestamp": r.Timestamp})
dgroups = collectionByKeys(dgroups).insert(finaltags,
map[string]interface{}{"_component_id": r.ComponentId})
dgroups = collectionByKeys(dgroups).insert(finaltags,
map[string]interface{}{"_subcomponent_id": r.SubComponentId})
}
return dgroups
}
// Takes in sensor configuration and converts it into slice of sensorConfig objects
func (m *OpenConfigTelemetry) splitSensorConfig() int {
var pathlist []*telemetry.Path
var measurementName string
var reportingRate uint32
m.sensorsConfig = make([]sensorConfig, 0)
for _, sensor := range m.Sensors {
spathSplit := strings.Fields(sensor)
reportingRate = uint32(time.Duration(m.SampleFrequency) / time.Millisecond)
// Extract measurement name and custom reporting rate if specified. Custom
// reporting rate will be specified at the beginning of sensor list,
// followed by measurement name like "1000ms interfaces /interfaces"
// where 1000ms is the custom reporting rate and interfaces is the
// measurement name. If 1000ms is not given, we use global reporting rate
// from sample_frequency. if measurement name is not given, we use first
// sensor name as the measurement name. If first or the word after custom
// reporting rate doesn't start with /, we treat it as measurement name
// and exclude it from list of sensors to subscribe
duration, err := time.ParseDuration(spathSplit[0])
if err == nil {
reportingRate = uint32(duration / time.Millisecond)
spathSplit = spathSplit[1:]
}
if len(spathSplit) == 0 {
m.Log.Error("No sensors are specified")
continue
}
// Word after custom reporting rate is treated as measurement name
measurementName = spathSplit[0]
// If our word after custom reporting rate doesn't start with /, we treat
// it as measurement name. Else we treat it as sensor
if !strings.HasPrefix(measurementName, "/") {
spathSplit = spathSplit[1:]
}
if len(spathSplit) == 0 {
m.Log.Error("No valid sensors are specified")
continue
}
// Iterate over our sensors and create pathlist to subscribe
pathlist = make([]*telemetry.Path, 0)
for _, path := range spathSplit {
pathlist = append(pathlist, &telemetry.Path{Path: path,
SampleFrequency: reportingRate})
}
m.sensorsConfig = append(m.sensorsConfig, sensorConfig{
measurementName: measurementName, pathList: pathlist,
})
}
return len(m.sensorsConfig)
}
// Subscribes and collects OpenConfig telemetry data from given server
func (m *OpenConfigTelemetry) collectData(
ctx context.Context,
grpcServer string,
grpcClientConn *grpc.ClientConn,
acc telegraf.Accumulator,
) {
c := telemetry.NewOpenConfigTelemetryClient(grpcClientConn)
for _, sensor := range m.sensorsConfig {
m.wg.Add(1)
go func(ctx context.Context, sensor sensorConfig) {
defer m.wg.Done()
for {
stream, err := c.TelemetrySubscribe(
ctx,
&telemetry.SubscriptionRequest{PathList: sensor.pathList},
)
if err != nil {
rpcStatus, _ := status.FromError(err)
if rpcStatus.Code() == codes.Unauthenticated {
if m.Username != "" && m.Password != "" && m.ClientID != "" {
err := m.authenticate(ctx, grpcServer, grpcClientConn)
if err == nil {
time.Sleep(1 * time.Second)
continue
}
acc.AddError(fmt.Errorf("could not re-authenticate: %w", err))
}
} else if rpcStatus.Code() != codes.Unavailable {
// If service is currently unavailable and may come back later, retry
acc.AddError(fmt.Errorf("could not subscribe to %s on %q: %w", sensor.measurementName, grpcServer, err))
return
}
// Retry with delay. If delay is not provided, use default
if time.Duration(m.RetryDelay) > 0 {
m.Log.Debugf("Retrying %s from %s with timeout %v", sensor.measurementName, grpcServer, time.Duration(m.RetryDelay))
time.Sleep(time.Duration(m.RetryDelay))
continue
}
return
}
m.Log.Debugf("Successfully subscribed to %s on %s", sensor.measurementName, grpcServer)
for {
r, err := stream.Recv()
if err != nil {
// If we encounter error in the stream, break so we can retry
// the connection
acc.AddError(fmt.Errorf("failed to read from %s from %s: %w", sensor.measurementName, grpcServer, err))
time.Sleep(1 * time.Second)
break
}
m.Log.Debugf("Received from %s on %s: %v", sensor.measurementName, grpcServer, r)
// Create a point and add to batch
tags := make(map[string]string)
// Insert additional tags
tags["device"] = grpcServer
dgroups := m.extractData(r, grpcServer)
// Print final data collection
m.Log.Debugf("Available collection for %s on %s: %v", sensor.measurementName, grpcServer, dgroups)
timestamp := time.Now()
// Iterate through data groups and add them
for _, group := range dgroups {
if m.TimestampSource == "data" {
// OpenConfig timestamp is in milliseconds since epoch
ts, ok := group.data["_timestamp"].(uint64)
if ok {
timestamp = time.UnixMilli(int64(ts))
} else {
m.Log.Warnf("Invalid type %T for _timestamp %v", group.data["_timestamp"], group.data["_timestamp"])
}
}
if len(group.tags) == 0 {
acc.AddFields(sensor.measurementName, group.data, tags, timestamp)
} else {
acc.AddFields(sensor.measurementName, group.data, group.tags, timestamp)
}
}
}
}
}(ctx, sensor)
}
}
func (m *OpenConfigTelemetry) authenticate(ctx context.Context, server string, grpcClientConn *grpc.ClientConn) error {
lc := authentication.NewLoginClient(grpcClientConn)
loginReply, err := lc.LoginCheck(
ctx,
&authentication.LoginRequest{
UserName: m.Username,
Password: m.Password,
ClientId: m.ClientID,
},
)
if err != nil {
return fmt.Errorf("could not initiate login check for %s: %w", server, err)
}
// Check if the user is authenticated. Bail if auth error
if !loginReply.Result {
return fmt.Errorf("failed to authenticate the user for %s", server)
}
return nil
}
func init() {
inputs.Add("jti_openconfig_telemetry", func() telegraf.Input {
return &OpenConfigTelemetry{
RetryDelay: config.Duration(time.Second),
KeepAlivePeriod: config.Duration(10 * time.Second),
StrAsTags: false,
TimestampSource: "collection",
}
})
}

View file

@ -0,0 +1,269 @@
package jti_openconfig_telemetry
import (
"context"
"log"
"net"
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"github.com/influxdata/telegraf/config"
telemetry "github.com/influxdata/telegraf/plugins/inputs/jti_openconfig_telemetry/oc"
"github.com/influxdata/telegraf/testutil"
)
var cfg = &OpenConfigTelemetry{
Log: testutil.Logger{},
Servers: []string{"127.0.0.1:50051"},
SampleFrequency: config.Duration(time.Millisecond * 10),
}
var data = &telemetry.OpenConfigData{
Path: "/sensor",
Kv: []*telemetry.KeyValue{{Key: "/sensor[tag='tagValue']/intKey", Value: &telemetry.KeyValue_IntValue{IntValue: 10}}},
}
var dataWithPrefix = &telemetry.OpenConfigData{
Path: "/sensor_with_prefix",
Kv: []*telemetry.KeyValue{{Key: "__prefix__", Value: &telemetry.KeyValue_StrValue{StrValue: "/sensor/prefix/"}},
{Key: "intKey", Value: &telemetry.KeyValue_IntValue{IntValue: 10}}},
}
var dataWithMultipleTags = &telemetry.OpenConfigData{
Path: "/sensor_with_multiple_tags",
Kv: []*telemetry.KeyValue{{Key: "__prefix__", Value: &telemetry.KeyValue_StrValue{StrValue: "/sensor/prefix/"}},
{Key: "tagKey[tag='tagValue']/boolKey", Value: &telemetry.KeyValue_BoolValue{BoolValue: false}},
{Key: "intKey", Value: &telemetry.KeyValue_IntValue{IntValue: 10}}},
}
var dataWithStringValues = &telemetry.OpenConfigData{
Path: "/sensor_with_string_values",
Kv: []*telemetry.KeyValue{{Key: "__prefix__", Value: &telemetry.KeyValue_StrValue{StrValue: "/sensor/prefix/"}},
{Key: "strKey[tag='tagValue']/strValue", Value: &telemetry.KeyValue_StrValue{StrValue: "10"}}},
}
var dataWithTimestamp = &telemetry.OpenConfigData{
Path: "/sensor_with_timestamp",
Kv: []*telemetry.KeyValue{
{Key: "/sensor[tag='tagValue']/intKey", Value: &telemetry.KeyValue_IntValue{IntValue: 10}},
},
Timestamp: 1676560510002,
}
type openConfigTelemetryServer struct {
telemetry.UnimplementedOpenConfigTelemetryServer
}
func (*openConfigTelemetryServer) TelemetrySubscribe(req *telemetry.SubscriptionRequest, stream telemetry.OpenConfigTelemetry_TelemetrySubscribeServer) error {
path := req.PathList[0].Path
switch path {
case "/sensor":
return stream.Send(data)
case "/sensor_with_prefix":
return stream.Send(dataWithPrefix)
case "/sensor_with_multiple_tags":
return stream.Send(dataWithMultipleTags)
case "/sensor_with_string_values":
return stream.Send(dataWithStringValues)
case "/sensor_with_timestamp":
return stream.Send(dataWithTimestamp)
}
return nil
}
func (*openConfigTelemetryServer) CancelTelemetrySubscription(
context.Context,
*telemetry.CancelSubscriptionRequest,
) (*telemetry.CancelSubscriptionReply, error) {
return nil, nil
}
func (*openConfigTelemetryServer) GetTelemetrySubscriptions(
context.Context,
*telemetry.GetSubscriptionsRequest,
) (*telemetry.GetSubscriptionsReply, error) {
return nil, nil
}
func (*openConfigTelemetryServer) GetTelemetryOperationalState(
context.Context,
*telemetry.GetOperationalStateRequest,
) (*telemetry.GetOperationalStateReply, error) {
return nil, nil
}
func (*openConfigTelemetryServer) GetDataEncodings(context.Context, *telemetry.DataEncodingRequest) (*telemetry.DataEncodingReply, error) {
return nil, nil
}
func newServer() *openConfigTelemetryServer {
s := new(openConfigTelemetryServer)
return s
}
func TestOpenConfigTelemetryData(t *testing.T) {
var acc testutil.Accumulator
cfg.Sensors = []string{"/sensor"}
err := cfg.Start(&acc)
require.NoError(t, err)
tags := map[string]string{
"device": "127.0.0.1",
"/sensor/@tag": "tagValue",
"system_id": "",
"path": "/sensor",
}
fields := map[string]interface{}{
"/sensor/intKey": int64(10),
"_sequence": uint64(0),
"_timestamp": uint64(0),
"_component_id": uint32(0),
"_subcomponent_id": uint32(0),
}
require.Eventually(t, func() bool { return acc.HasMeasurement("/sensor") }, 5*time.Second, 10*time.Millisecond)
acc.AssertContainsTaggedFields(t, "/sensor", fields, tags)
}
func TestOpenConfigTelemetryData_timestamp(t *testing.T) {
var acc testutil.Accumulator
cfg.Sensors = []string{"/sensor_with_timestamp"}
require.NoError(t, cfg.Start(&acc))
timestamp := int64(1676560510002)
tags := map[string]string{
"device": "127.0.0.1",
"/sensor/@tag": "tagValue",
"system_id": "",
"path": "/sensor_with_timestamp",
}
fields := map[string]interface{}{
"/sensor/intKey": int64(10),
"_sequence": uint64(0),
"_timestamp": uint64(timestamp),
"_component_id": uint32(0),
"_subcomponent_id": uint32(0),
}
require.Eventually(t, func() bool { return acc.HasMeasurement("/sensor_with_timestamp") }, 5*time.Second, 10*time.Millisecond)
acc.AssertContainsTaggedFields(t, "/sensor_with_timestamp", fields, tags)
}
func TestOpenConfigTelemetryDataWithPrefix(t *testing.T) {
var acc testutil.Accumulator
cfg.Sensors = []string{"/sensor_with_prefix"}
err := cfg.Start(&acc)
require.NoError(t, err)
tags := map[string]string{
"device": "127.0.0.1",
"system_id": "",
"path": "/sensor_with_prefix",
}
fields := map[string]interface{}{
"/sensor/prefix/intKey": int64(10),
"_sequence": uint64(0),
"_timestamp": uint64(0),
"_component_id": uint32(0),
"_subcomponent_id": uint32(0),
}
require.Eventually(t, func() bool { return acc.HasMeasurement("/sensor_with_prefix") }, 5*time.Second, 10*time.Millisecond)
acc.AssertContainsTaggedFields(t, "/sensor_with_prefix", fields, tags)
}
func TestOpenConfigTelemetryDataWithMultipleTags(t *testing.T) {
var acc testutil.Accumulator
cfg.Sensors = []string{"/sensor_with_multiple_tags"}
err := cfg.Start(&acc)
require.NoError(t, err)
tags1 := map[string]string{
"/sensor/prefix/tagKey/@tag": "tagValue",
"device": "127.0.0.1",
"system_id": "",
"path": "/sensor_with_multiple_tags",
}
fields1 := map[string]interface{}{
"/sensor/prefix/tagKey/boolKey": false,
"_sequence": uint64(0),
"_timestamp": uint64(0),
"_component_id": uint32(0),
"_subcomponent_id": uint32(0),
}
tags2 := map[string]string{
"device": "127.0.0.1",
"system_id": "",
"path": "/sensor_with_multiple_tags",
}
fields2 := map[string]interface{}{
"/sensor/prefix/intKey": int64(10),
"_sequence": uint64(0),
"_timestamp": uint64(0),
"_component_id": uint32(0),
"_subcomponent_id": uint32(0),
}
require.Eventually(t, func() bool { return acc.HasMeasurement("/sensor_with_multiple_tags") }, 5*time.Second, 10*time.Millisecond)
acc.AssertContainsTaggedFields(t, "/sensor_with_multiple_tags", fields1, tags1)
acc.AssertContainsTaggedFields(t, "/sensor_with_multiple_tags", fields2, tags2)
}
func TestOpenConfigTelemetryDataWithStringValues(t *testing.T) {
var acc testutil.Accumulator
cfg.Sensors = []string{"/sensor_with_string_values"}
err := cfg.Start(&acc)
require.NoError(t, err)
tags := map[string]string{
"/sensor/prefix/strKey/@tag": "tagValue",
"device": "127.0.0.1",
"system_id": "",
"path": "/sensor_with_string_values",
}
fields := map[string]interface{}{
"/sensor/prefix/strKey/strValue": "10",
"_sequence": uint64(0),
"_timestamp": uint64(0),
"_component_id": uint32(0),
"_subcomponent_id": uint32(0),
}
require.Eventually(t, func() bool { return acc.HasMeasurement("/sensor_with_string_values") }, 5*time.Second, 10*time.Millisecond)
acc.AssertContainsTaggedFields(t, "/sensor_with_string_values", fields, tags)
}
func TestMain(m *testing.M) {
lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
exitCode := 0
defer func() {
os.Exit(exitCode)
}()
cfg.Servers = []string{lis.Addr().String()}
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
telemetry.RegisterOpenConfigTelemetryServer(grpcServer, newServer())
go func() {
grpcServer.Serve(lis) //nolint:errcheck // ignore the returned error as the tests will fail anyway
}()
defer grpcServer.Stop()
exitCode = m.Run()
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,320 @@
//
// Copyrights (c) 2016, Juniper Networks, Inc.
// All rights reserved.
//
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
//
// Nitin Kumar 04/07/2016
// Abbas Sakarwala 04/07/2016
//
// This file defines the Openconfig Telemetry RPC APIs (for gRPC).
//
// https://github.com/openconfig/public/blob/master/release/models/rpc/openconfig-rpc-api.yang
//
// Version 1.0
//
syntax = "proto3";
package telemetry;
option go_package = ".;telemetry";
// Interface exported by Agent
service OpenConfigTelemetry {
// Request an inline subscription for data at the specified path.
// The device should send telemetry data back on the same
// connection as the subscription request.
rpc telemetrySubscribe(SubscriptionRequest) returns (stream OpenConfigData) {}
// Terminates and removes an existing telemetry subscription
rpc cancelTelemetrySubscription(CancelSubscriptionRequest) returns (CancelSubscriptionReply) {}
// Get the list of current telemetry subscriptions from the
// target. This command returns a list of existing subscriptions
// not including those that are established via configuration.
rpc getTelemetrySubscriptions(GetSubscriptionsRequest) returns (GetSubscriptionsReply) {}
// Get Telemetry Agent Operational States
rpc getTelemetryOperationalState(GetOperationalStateRequest) returns (GetOperationalStateReply) {}
// Return the set of data encodings supported by the device for
// telemetry data
rpc getDataEncodings(DataEncodingRequest) returns (DataEncodingReply) {}
}
// Message sent for a telemetry subscription request
message SubscriptionRequest {
// Data associated with a telemetry subscription
SubscriptionInput input = 1;
// List of data models paths and filters
// which are used in a telemetry operation.
repeated Path path_list = 2;
// The below configuration is not defined in Openconfig RPC.
// It is a proposed extension to configure additional
// subscription request features.
SubscriptionAdditionalConfig additional_config = 3;
}
// Data associated with a telemetry subscription
message SubscriptionInput {
// List of optional collector endpoints to send data for
// this subscription.
// If no collector destinations are specified, the collector
// destination is assumed to be the requester on the rpc channel.
repeated Collector collector_list = 1;
}
// Collector endpoints to send data specified as an ip+port combination.
message Collector {
// IP address of collector endpoint
string address = 1;
// Transport protocol port number for the collector destination.
uint32 port = 2;
}
// Data model path
message Path {
// Data model path of interest
// Path specification for elements of OpenConfig data models
string path = 1;
// Regular expression to be used in filtering state leaves
string filter = 2;
// If this is set to true, the target device will only send
// updates to the collector upon a change in data value
bool suppress_unchanged = 3;
// Maximum time in ms the target device may go without sending
// a message to the collector. If this time expires with
// suppress-unchanged set, the target device must send an update
// message regardless if the data values have changed.
uint32 max_silent_interval = 4;
// Time in ms between collection and transmission of the
// specified data to the collector platform. The target device
// will sample the corresponding data (e.g,. a counter) and
// immediately send to the collector destination.
//
// If sample-frequency is set to 0, then the network device
// must emit an update upon every datum change.
uint32 sample_frequency = 5;
// EOM needed for each walk cycle of this path?
// For periodic sensor, applicable for each complete reap
// For event sensor, applicable when initial dump is over
// (same as EOS)
// This feature is not implemented currently.
bool need_eom = 6;
}
// Configure subscription request additional features.
message SubscriptionAdditionalConfig {
// limit the number of records sent in the stream
int32 limit_records = 1;
// limit the time the stream remains open
int32 limit_time_seconds = 2;
// EOS needed for this subscription?
bool need_eos = 3;
}
// Reply to inline subscription for data at the specified path is done in
// two-folds.
// 1. Reply data message sent out using out-of-band channel.
// 2. Telemetry data send back on the same connection as the
// subscription request.
// 1. Reply data message sent out using out-of-band channel.
message SubscriptionReply {
// Response message to a telemetry subscription creation or
// get request.
SubscriptionResponse response = 1;
// List of data models paths and filters
// which are used in a telemetry operation.
repeated Path path_list = 2;
}
// Response message to a telemetry subscription creation or get request.
message SubscriptionResponse {
// Unique id for the subscription on the device. This is
// generated by the device and returned in a subscription
// request or when listing existing subscriptions
uint32 subscription_id = 1;
}
// 2. Telemetry data send back on the same connection as the
// subscription request.
message OpenConfigData {
// router name:export IP address
string system_id = 1;
// line card / RE (slot number)
uint32 component_id = 2;
// PFE (if applicable)
uint32 sub_component_id = 3;
// Path specification for elements of OpenConfig data models
string path = 4;
// Sequence number, monotonically increasing for each
// system_id, component_id, sub_component_id + path.
uint64 sequence_number = 5;
// timestamp (milliseconds since epoch)
uint64 timestamp = 6;
// List of key-value pairs
repeated KeyValue kv = 7;
// For delete. If filled, it indicates delete
repeated Delete delete = 8;
// If filled, it indicates end of marker for the
// respective path in the list.
repeated Eom eom = 9;
// If filled, it indicates end of sync for complete subscription
bool sync_response = 10;
}
// Simple Key-value, where value could be one of scalar types
message KeyValue {
// Key
string key = 1;
// One of possible values
oneof value {
double double_value = 5;
int64 int_value = 6;
uint64 uint_value = 7;
sint64 sint_value = 8;
bool bool_value = 9;
string str_value = 10;
bytes bytes_value = 11;
}
}
// Message indicating delete for a particular path
message Delete {
string path = 1;
}
// Message indicating EOM for a particular path
message Eom {
string path = 1;
}
// Message sent for a telemetry subscription cancellation request
message CancelSubscriptionRequest {
// Subscription identifier as returned by the device when
// subscription was requested
uint32 subscription_id = 1;
}
// Reply to telemetry subscription cancellation request
message CancelSubscriptionReply {
// Return code
ReturnCode code = 1;
// Return code string
string code_str = 2;
};
// Result of the operation
enum ReturnCode {
SUCCESS = 0;
NO_SUBSCRIPTION_ENTRY = 1;
UNKNOWN_ERROR = 2;
}
// Message sent for a telemetry get request
message GetSubscriptionsRequest {
// Subscription identifier as returned by the device when
// subscription was requested
// --- or ---
// 0xFFFFFFFF for all subscription identifiers
uint32 subscription_id = 1;
}
// Reply to telemetry subscription get request
message GetSubscriptionsReply {
// List of current telemetry subscriptions
repeated SubscriptionReply subscription_list = 1;
}
// Message sent for telemetry agent operational states request
message GetOperationalStateRequest {
// Per-subscription_id level operational state can be requested.
//
// Subscription identifier as returned by the device when
// subscription was requested
// --- or ---
// 0xFFFFFFFF for all subscription identifiers including agent-level
// operational stats
// --- or ---
// If subscription_id is not present then sent only agent-level
// operational stats
uint32 subscription_id = 1;
// Control verbosity of the output
VerbosityLevel verbosity = 2;
}
// Verbosity Level
enum VerbosityLevel {
DETAIL = 0;
TERSE = 1;
BRIEF = 2;
}
// Reply to telemetry agent operational states request
message GetOperationalStateReply {
// List of key-value pairs where
// key = operational state definition
// value = operational state value
repeated KeyValue kv = 1;
}
// Message sent for a data encoding request
message DataEncodingRequest {
}
// Reply to data encodings supported request
message DataEncodingReply {
repeated EncodingType encoding_list = 1;
}
// Encoding Type Supported
enum EncodingType {
UNDEFINED = 0;
XML = 1;
JSON_IETF = 2;
PROTO3 = 3;
}

View file

@ -0,0 +1,294 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package telemetry
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// OpenConfigTelemetryClient is the client API for OpenConfigTelemetry service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type OpenConfigTelemetryClient interface {
// Request an inline subscription for data at the specified path.
// The device should send telemetry data back on the same
// connection as the subscription request.
TelemetrySubscribe(ctx context.Context, in *SubscriptionRequest, opts ...grpc.CallOption) (OpenConfigTelemetry_TelemetrySubscribeClient, error)
// Terminates and removes an existing telemetry subscription
CancelTelemetrySubscription(ctx context.Context, in *CancelSubscriptionRequest, opts ...grpc.CallOption) (*CancelSubscriptionReply, error)
// Get the list of current telemetry subscriptions from the
// target. This command returns a list of existing subscriptions
// not including those that are established via configuration.
GetTelemetrySubscriptions(ctx context.Context, in *GetSubscriptionsRequest, opts ...grpc.CallOption) (*GetSubscriptionsReply, error)
// Get Telemetry Agent Operational States
GetTelemetryOperationalState(ctx context.Context, in *GetOperationalStateRequest, opts ...grpc.CallOption) (*GetOperationalStateReply, error)
// Return the set of data encodings supported by the device for
// telemetry data
GetDataEncodings(ctx context.Context, in *DataEncodingRequest, opts ...grpc.CallOption) (*DataEncodingReply, error)
}
type openConfigTelemetryClient struct {
cc grpc.ClientConnInterface
}
func NewOpenConfigTelemetryClient(cc grpc.ClientConnInterface) OpenConfigTelemetryClient {
return &openConfigTelemetryClient{cc}
}
func (c *openConfigTelemetryClient) TelemetrySubscribe(ctx context.Context, in *SubscriptionRequest, opts ...grpc.CallOption) (OpenConfigTelemetry_TelemetrySubscribeClient, error) {
stream, err := c.cc.NewStream(ctx, &OpenConfigTelemetry_ServiceDesc.Streams[0], "/telemetry.OpenConfigTelemetry/telemetrySubscribe", opts...)
if err != nil {
return nil, err
}
x := &openConfigTelemetryTelemetrySubscribeClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type OpenConfigTelemetry_TelemetrySubscribeClient interface {
Recv() (*OpenConfigData, error)
grpc.ClientStream
}
type openConfigTelemetryTelemetrySubscribeClient struct {
grpc.ClientStream
}
func (x *openConfigTelemetryTelemetrySubscribeClient) Recv() (*OpenConfigData, error) {
m := new(OpenConfigData)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *openConfigTelemetryClient) CancelTelemetrySubscription(ctx context.Context, in *CancelSubscriptionRequest, opts ...grpc.CallOption) (*CancelSubscriptionReply, error) {
out := new(CancelSubscriptionReply)
err := c.cc.Invoke(ctx, "/telemetry.OpenConfigTelemetry/cancelTelemetrySubscription", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *openConfigTelemetryClient) GetTelemetrySubscriptions(ctx context.Context, in *GetSubscriptionsRequest, opts ...grpc.CallOption) (*GetSubscriptionsReply, error) {
out := new(GetSubscriptionsReply)
err := c.cc.Invoke(ctx, "/telemetry.OpenConfigTelemetry/getTelemetrySubscriptions", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *openConfigTelemetryClient) GetTelemetryOperationalState(ctx context.Context, in *GetOperationalStateRequest, opts ...grpc.CallOption) (*GetOperationalStateReply, error) {
out := new(GetOperationalStateReply)
err := c.cc.Invoke(ctx, "/telemetry.OpenConfigTelemetry/getTelemetryOperationalState", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *openConfigTelemetryClient) GetDataEncodings(ctx context.Context, in *DataEncodingRequest, opts ...grpc.CallOption) (*DataEncodingReply, error) {
out := new(DataEncodingReply)
err := c.cc.Invoke(ctx, "/telemetry.OpenConfigTelemetry/getDataEncodings", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// OpenConfigTelemetryServer is the server API for OpenConfigTelemetry service.
// All implementations must embed UnimplementedOpenConfigTelemetryServer
// for forward compatibility
type OpenConfigTelemetryServer interface {
// Request an inline subscription for data at the specified path.
// The device should send telemetry data back on the same
// connection as the subscription request.
TelemetrySubscribe(*SubscriptionRequest, OpenConfigTelemetry_TelemetrySubscribeServer) error
// Terminates and removes an existing telemetry subscription
CancelTelemetrySubscription(context.Context, *CancelSubscriptionRequest) (*CancelSubscriptionReply, error)
// Get the list of current telemetry subscriptions from the
// target. This command returns a list of existing subscriptions
// not including those that are established via configuration.
GetTelemetrySubscriptions(context.Context, *GetSubscriptionsRequest) (*GetSubscriptionsReply, error)
// Get Telemetry Agent Operational States
GetTelemetryOperationalState(context.Context, *GetOperationalStateRequest) (*GetOperationalStateReply, error)
// Return the set of data encodings supported by the device for
// telemetry data
GetDataEncodings(context.Context, *DataEncodingRequest) (*DataEncodingReply, error)
mustEmbedUnimplementedOpenConfigTelemetryServer()
}
// UnimplementedOpenConfigTelemetryServer must be embedded to have forward compatible implementations.
type UnimplementedOpenConfigTelemetryServer struct {
}
func (UnimplementedOpenConfigTelemetryServer) TelemetrySubscribe(*SubscriptionRequest, OpenConfigTelemetry_TelemetrySubscribeServer) error {
return status.Errorf(codes.Unimplemented, "method TelemetrySubscribe not implemented")
}
func (UnimplementedOpenConfigTelemetryServer) CancelTelemetrySubscription(context.Context, *CancelSubscriptionRequest) (*CancelSubscriptionReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method CancelTelemetrySubscription not implemented")
}
func (UnimplementedOpenConfigTelemetryServer) GetTelemetrySubscriptions(context.Context, *GetSubscriptionsRequest) (*GetSubscriptionsReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTelemetrySubscriptions not implemented")
}
func (UnimplementedOpenConfigTelemetryServer) GetTelemetryOperationalState(context.Context, *GetOperationalStateRequest) (*GetOperationalStateReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTelemetryOperationalState not implemented")
}
func (UnimplementedOpenConfigTelemetryServer) GetDataEncodings(context.Context, *DataEncodingRequest) (*DataEncodingReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDataEncodings not implemented")
}
func (UnimplementedOpenConfigTelemetryServer) mustEmbedUnimplementedOpenConfigTelemetryServer() {}
// UnsafeOpenConfigTelemetryServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to OpenConfigTelemetryServer will
// result in compilation errors.
type UnsafeOpenConfigTelemetryServer interface {
mustEmbedUnimplementedOpenConfigTelemetryServer()
}
func RegisterOpenConfigTelemetryServer(s grpc.ServiceRegistrar, srv OpenConfigTelemetryServer) {
s.RegisterService(&OpenConfigTelemetry_ServiceDesc, srv)
}
func _OpenConfigTelemetry_TelemetrySubscribe_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscriptionRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(OpenConfigTelemetryServer).TelemetrySubscribe(m, &openConfigTelemetryTelemetrySubscribeServer{stream})
}
type OpenConfigTelemetry_TelemetrySubscribeServer interface {
Send(*OpenConfigData) error
grpc.ServerStream
}
type openConfigTelemetryTelemetrySubscribeServer struct {
grpc.ServerStream
}
func (x *openConfigTelemetryTelemetrySubscribeServer) Send(m *OpenConfigData) error {
return x.ServerStream.SendMsg(m)
}
func _OpenConfigTelemetry_CancelTelemetrySubscription_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CancelSubscriptionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OpenConfigTelemetryServer).CancelTelemetrySubscription(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/telemetry.OpenConfigTelemetry/cancelTelemetrySubscription",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OpenConfigTelemetryServer).CancelTelemetrySubscription(ctx, req.(*CancelSubscriptionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OpenConfigTelemetry_GetTelemetrySubscriptions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetSubscriptionsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OpenConfigTelemetryServer).GetTelemetrySubscriptions(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/telemetry.OpenConfigTelemetry/getTelemetrySubscriptions",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OpenConfigTelemetryServer).GetTelemetrySubscriptions(ctx, req.(*GetSubscriptionsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OpenConfigTelemetry_GetTelemetryOperationalState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetOperationalStateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OpenConfigTelemetryServer).GetTelemetryOperationalState(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/telemetry.OpenConfigTelemetry/getTelemetryOperationalState",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OpenConfigTelemetryServer).GetTelemetryOperationalState(ctx, req.(*GetOperationalStateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OpenConfigTelemetry_GetDataEncodings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DataEncodingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OpenConfigTelemetryServer).GetDataEncodings(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/telemetry.OpenConfigTelemetry/getDataEncodings",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OpenConfigTelemetryServer).GetDataEncodings(ctx, req.(*DataEncodingRequest))
}
return interceptor(ctx, in, info, handler)
}
// OpenConfigTelemetry_ServiceDesc is the grpc.ServiceDesc for OpenConfigTelemetry service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var OpenConfigTelemetry_ServiceDesc = grpc.ServiceDesc{
ServiceName: "telemetry.OpenConfigTelemetry",
HandlerType: (*OpenConfigTelemetryServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "cancelTelemetrySubscription",
Handler: _OpenConfigTelemetry_CancelTelemetrySubscription_Handler,
},
{
MethodName: "getTelemetrySubscriptions",
Handler: _OpenConfigTelemetry_GetTelemetrySubscriptions_Handler,
},
{
MethodName: "getTelemetryOperationalState",
Handler: _OpenConfigTelemetry_GetTelemetryOperationalState_Handler,
},
{
MethodName: "getDataEncodings",
Handler: _OpenConfigTelemetry_GetDataEncodings_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "telemetrySubscribe",
Handler: _OpenConfigTelemetry_TelemetrySubscribe_Handler,
ServerStreams: true,
},
},
Metadata: "oc/oc.proto",
}

View file

@ -0,0 +1,60 @@
# Subscribe and receive OpenConfig Telemetry data using JTI
[[inputs.jti_openconfig_telemetry]]
## List of device addresses to collect telemetry from
servers = ["localhost:1883"]
## Authentication details. Username and password are must if device expects
## authentication. Client ID must be unique when connecting from multiple instances
## of telegraf to the same device
username = "user"
password = "pass"
client_id = "telegraf"
## Frequency to get data
sample_frequency = "1000ms"
## Sensors to subscribe for
## A identifier for each sensor can be provided in path by separating with space
## Else sensor path will be used as identifier
## When identifier is used, we can provide a list of space separated sensors.
## A single subscription will be created with all these sensors and data will
## be saved to measurement with this identifier name
sensors = [
"/interfaces/",
"collection /components/ /lldp",
]
## We allow specifying sensor group level reporting rate. To do this, specify the
## reporting rate in Duration at the beginning of sensor paths / collection
## name. For entries without reporting rate, we use configured sample frequency
sensors = [
"1000ms customReporting /interfaces /lldp",
"2000ms collection /components",
"/interfaces",
]
## Timestamp Source
## Set to 'collection' for time of collection, and 'data' for using the time
## provided by the _timestamp field.
# timestamp_source = "collection"
## Optional TLS Config
# enable_tls = false
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Minimal TLS version to accept by the client
# tls_min_version = "TLS12"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
## Delay between retry attempts of failed RPC calls or streams. Defaults to 1000ms.
## Failed streams/calls will not be retried if 0 is provided
retry_delay = "1000ms"
## Period for sending keep-alive packets on idle connections
## This is helpful to identify broken connections to the server
# keep_alive_period = "10s"
## To treat all string values as tags, set this to true
str_as_tags = false